summaryrefslogtreecommitdiffstats
path: root/sound/pci
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sound/pci/Kconfig914
-rw-r--r--sound/pci/Makefile83
-rw-r--r--sound/pci/ac97/Makefile11
-rw-r--r--sound/pci/ac97/ac97_codec.c2924
-rw-r--r--sound/pci/ac97/ac97_id.h51
-rw-r--r--sound/pci/ac97/ac97_local.h26
-rw-r--r--sound/pci/ac97/ac97_patch.c3932
-rw-r--r--sound/pci/ac97/ac97_patch.h80
-rw-r--r--sound/pci/ac97/ac97_pcm.c737
-rw-r--r--sound/pci/ac97/ac97_proc.c467
-rw-r--r--sound/pci/ad1889.c1010
-rw-r--r--sound/pci/ad1889.h190
-rw-r--r--sound/pci/ak4531_codec.c470
-rw-r--r--sound/pci/ali5451/Makefile10
-rw-r--r--sound/pci/ali5451/ali5451.c2232
-rw-r--r--sound/pci/als300.c782
-rw-r--r--sound/pci/als4000.c1002
-rw-r--r--sound/pci/asihpi/Makefile6
-rw-r--r--sound/pci/asihpi/asihpi.c2980
-rw-r--r--sound/pci/asihpi/hpi.h1724
-rw-r--r--sound/pci/asihpi/hpi6000.c1801
-rw-r--r--sound/pci/asihpi/hpi6000.h59
-rw-r--r--sound/pci/asihpi/hpi6205.c2219
-rw-r--r--sound/pci/asihpi/hpi6205.h92
-rw-r--r--sound/pci/asihpi/hpi_internal.h1424
-rw-r--r--sound/pci/asihpi/hpi_version.h33
-rw-r--r--sound/pci/asihpi/hpicmn.c713
-rw-r--r--sound/pci/asihpi/hpicmn.h71
-rw-r--r--sound/pci/asihpi/hpidebug.c67
-rw-r--r--sound/pci/asihpi/hpidebug.h91
-rw-r--r--sound/pci/asihpi/hpidspcd.c131
-rw-r--r--sound/pci/asihpi/hpidspcd.h95
-rw-r--r--sound/pci/asihpi/hpifunc.c2869
-rw-r--r--sound/pci/asihpi/hpimsginit.c120
-rw-r--r--sound/pci/asihpi/hpimsginit.h35
-rw-r--r--sound/pci/asihpi/hpimsgx.c798
-rw-r--r--sound/pci/asihpi/hpimsgx.h25
-rw-r--r--sound/pci/asihpi/hpioctl.c594
-rw-r--r--sound/pci/asihpi/hpioctl.h27
-rw-r--r--sound/pci/asihpi/hpios.c72
-rw-r--r--sound/pci/asihpi/hpios.h154
-rw-r--r--sound/pci/asihpi/hpipcida.h26
-rw-r--r--sound/pci/atiixp.c1677
-rw-r--r--sound/pci/atiixp_modem.c1302
-rw-r--r--sound/pci/au88x0/Makefile8
-rw-r--r--sound/pci/au88x0/au8810.c17
-rw-r--r--sound/pci/au88x0/au8810.h225
-rw-r--r--sound/pci/au88x0/au8820.c15
-rw-r--r--sound/pci/au88x0/au8820.h205
-rw-r--r--sound/pci/au88x0/au8830.c18
-rw-r--r--sound/pci/au88x0/au8830.h252
-rw-r--r--sound/pci/au88x0/au88x0.c384
-rw-r--r--sound/pci/au88x0/au88x0.h281
-rw-r--r--sound/pci/au88x0/au88x0_a3d.c902
-rw-r--r--sound/pci/au88x0/au88x0_a3d.h111
-rw-r--r--sound/pci/au88x0/au88x0_a3ddata.c79
-rw-r--r--sound/pci/au88x0/au88x0_core.c2861
-rw-r--r--sound/pci/au88x0/au88x0_eq.c918
-rw-r--r--sound/pci/au88x0/au88x0_eq.h44
-rw-r--r--sound/pci/au88x0/au88x0_eqdata.c117
-rw-r--r--sound/pci/au88x0/au88x0_game.c121
-rw-r--r--sound/pci/au88x0/au88x0_mixer.c44
-rw-r--r--sound/pci/au88x0/au88x0_mpu401.c98
-rw-r--r--sound/pci/au88x0/au88x0_pcm.c681
-rw-r--r--sound/pci/au88x0/au88x0_synth.c400
-rw-r--r--sound/pci/au88x0/au88x0_wt.h66
-rw-r--r--sound/pci/au88x0/au88x0_xtalk.c783
-rw-r--r--sound/pci/au88x0/au88x0_xtalk.h49
-rw-r--r--sound/pci/aw2/Makefile4
-rw-r--r--sound/pci/aw2/aw2-alsa.c710
-rw-r--r--sound/pci/aw2/aw2-saa7146.c448
-rw-r--r--sound/pci/aw2/aw2-saa7146.h92
-rw-r--r--sound/pci/aw2/aw2-tsl.c97
-rw-r--r--sound/pci/aw2/saa7146.h155
-rw-r--r--sound/pci/azt3328.c2719
-rw-r--r--sound/pci/azt3328.h343
-rw-r--r--sound/pci/bt87x.c967
-rw-r--r--sound/pci/ca0106/Makefile5
-rw-r--r--sound/pci/ca0106/ca0106.h727
-rw-r--r--sound/pci/ca0106/ca0106_main.c1899
-rw-r--r--sound/pci/ca0106/ca0106_mixer.c918
-rw-r--r--sound/pci/ca0106/ca0106_proc.c429
-rw-r--r--sound/pci/ca0106/ca_midi.c301
-rw-r--r--sound/pci/ca0106/ca_midi.h52
-rw-r--r--sound/pci/cmipci.c3367
-rw-r--r--sound/pci/cs4281.c2046
-rw-r--r--sound/pci/cs46xx/Makefile11
-rw-r--r--sound/pci/cs46xx/cs46xx.c161
-rw-r--r--sound/pci/cs46xx/cs46xx.h1734
-rw-r--r--sound/pci/cs46xx/cs46xx_dsp_scb_types.h1198
-rw-r--r--sound/pci/cs46xx/cs46xx_dsp_spos.h213
-rw-r--r--sound/pci/cs46xx/cs46xx_dsp_task_types.h237
-rw-r--r--sound/pci/cs46xx/cs46xx_lib.c4029
-rw-r--r--sound/pci/cs46xx/cs46xx_lib.h195
-rw-r--r--sound/pci/cs46xx/dsp_spos.c1982
-rw-r--r--sound/pci/cs46xx/dsp_spos.h216
-rw-r--r--sound/pci/cs46xx/dsp_spos_scb_lib.c1777
-rw-r--r--sound/pci/cs5530.c289
-rw-r--r--sound/pci/cs5535audio/Makefile11
-rw-r--r--sound/pci/cs5535audio/cs5535audio.c397
-rw-r--r--sound/pci/cs5535audio/cs5535audio.h140
-rw-r--r--sound/pci/cs5535audio/cs5535audio_olpc.c188
-rw-r--r--sound/pci/cs5535audio/cs5535audio_pcm.c435
-rw-r--r--sound/pci/cs5535audio/cs5535audio_pm.c100
-rw-r--r--sound/pci/ctxfi/Makefile6
-rw-r--r--sound/pci/ctxfi/ct20k1reg.h631
-rw-r--r--sound/pci/ctxfi/ct20k2reg.h86
-rw-r--r--sound/pci/ctxfi/ctamixer.c484
-rw-r--r--sound/pci/ctxfi/ctamixer.h95
-rw-r--r--sound/pci/ctxfi/ctatc.c1748
-rw-r--r--sound/pci/ctxfi/ctatc.h156
-rw-r--r--sound/pci/ctxfi/ctdaio.c755
-rw-r--r--sound/pci/ctxfi/ctdaio.h121
-rw-r--r--sound/pci/ctxfi/cthardware.c89
-rw-r--r--sound/pci/ctxfi/cthardware.h214
-rw-r--r--sound/pci/ctxfi/cthw20k1.c2281
-rw-r--r--sound/pci/ctxfi/cthw20k1.h22
-rw-r--r--sound/pci/ctxfi/cthw20k2.c2350
-rw-r--r--sound/pci/ctxfi/cthw20k2.h22
-rw-r--r--sound/pci/ctxfi/ctimap.c108
-rw-r--r--sound/pci/ctxfi/ctimap.h36
-rw-r--r--sound/pci/ctxfi/ctmixer.c1225
-rw-r--r--sound/pci/ctxfi/ctmixer.h66
-rw-r--r--sound/pci/ctxfi/ctpcm.c476
-rw-r--r--sound/pci/ctxfi/ctpcm.h23
-rw-r--r--sound/pci/ctxfi/ctresource.c289
-rw-r--r--sound/pci/ctxfi/ctresource.h69
-rw-r--r--sound/pci/ctxfi/ctsrc.c882
-rw-r--r--sound/pci/ctxfi/ctsrc.h148
-rw-r--r--sound/pci/ctxfi/cttimer.c439
-rw-r--r--sound/pci/ctxfi/cttimer.h30
-rw-r--r--sound/pci/ctxfi/ctvmem.c242
-rw-r--r--sound/pci/ctxfi/ctvmem.h60
-rw-r--r--sound/pci/ctxfi/xfi.c159
-rw-r--r--sound/pci/echoaudio/Makefile35
-rw-r--r--sound/pci/echoaudio/darla20.c89
-rw-r--r--sound/pci/echoaudio/darla20_dsp.c129
-rw-r--r--sound/pci/echoaudio/darla24.c96
-rw-r--r--sound/pci/echoaudio/darla24_dsp.c163
-rw-r--r--sound/pci/echoaudio/echo3g.c110
-rw-r--r--sound/pci/echoaudio/echo3g_dsp.c131
-rw-r--r--sound/pci/echoaudio/echoaudio.c2290
-rw-r--r--sound/pci/echoaudio/echoaudio.h566
-rw-r--r--sound/pci/echoaudio/echoaudio_3g.c433
-rw-r--r--sound/pci/echoaudio/echoaudio_dsp.c1152
-rw-r--r--sound/pci/echoaudio/echoaudio_dsp.h698
-rw-r--r--sound/pci/echoaudio/echoaudio_gml.c203
-rw-r--r--sound/pci/echoaudio/gina20.c93
-rw-r--r--sound/pci/echoaudio/gina20_dsp.c215
-rw-r--r--sound/pci/echoaudio/gina24.c117
-rw-r--r--sound/pci/echoaudio/gina24_dsp.c345
-rw-r--r--sound/pci/echoaudio/indigo.c95
-rw-r--r--sound/pci/echoaudio/indigo_dsp.c165
-rw-r--r--sound/pci/echoaudio/indigo_express_dsp.c122
-rw-r--r--sound/pci/echoaudio/indigodj.c95
-rw-r--r--sound/pci/echoaudio/indigodj_dsp.c165
-rw-r--r--sound/pci/echoaudio/indigodjx.c96
-rw-r--r--sound/pci/echoaudio/indigodjx_dsp.c70
-rw-r--r--sound/pci/echoaudio/indigoio.c96
-rw-r--r--sound/pci/echoaudio/indigoio_dsp.c135
-rw-r--r--sound/pci/echoaudio/indigoiox.c98
-rw-r--r--sound/pci/echoaudio/indigoiox_dsp.c70
-rw-r--r--sound/pci/echoaudio/layla20.c103
-rw-r--r--sound/pci/echoaudio/layla20_dsp.c289
-rw-r--r--sound/pci/echoaudio/layla24.c115
-rw-r--r--sound/pci/echoaudio/layla24_dsp.c394
-rw-r--r--sound/pci/echoaudio/mia.c109
-rw-r--r--sound/pci/echoaudio/mia_dsp.c225
-rw-r--r--sound/pci/echoaudio/midi.c326
-rw-r--r--sound/pci/echoaudio/mona.c126
-rw-r--r--sound/pci/echoaudio/mona_dsp.c421
-rw-r--r--sound/pci/emu10k1/Makefile17
-rw-r--r--sound/pci/emu10k1/emu10k1.c262
-rw-r--r--sound/pci/emu10k1/emu10k1_callback.c537
-rw-r--r--sound/pci/emu10k1/emu10k1_main.c2152
-rw-r--r--sound/pci/emu10k1/emu10k1_patch.c216
-rw-r--r--sound/pci/emu10k1/emu10k1_synth.c106
-rw-r--r--sound/pci/emu10k1/emu10k1_synth_local.h29
-rw-r--r--sound/pci/emu10k1/emu10k1x.c1617
-rw-r--r--sound/pci/emu10k1/emufx.c2710
-rw-r--r--sound/pci/emu10k1/emumixer.c2220
-rw-r--r--sound/pci/emu10k1/emumpu401.c384
-rw-r--r--sound/pci/emu10k1/emupcm.c1826
-rw-r--r--sound/pci/emu10k1/emuproc.c623
-rw-r--r--sound/pci/emu10k1/io.c566
-rw-r--r--sound/pci/emu10k1/irq.c199
-rw-r--r--sound/pci/emu10k1/memory.c621
-rw-r--r--sound/pci/emu10k1/p16v.c856
-rw-r--r--sound/pci/emu10k1/p16v.h284
-rw-r--r--sound/pci/emu10k1/p17v.h144
-rw-r--r--sound/pci/emu10k1/timer.c82
-rw-r--r--sound/pci/emu10k1/tina2.h18
-rw-r--r--sound/pci/emu10k1/voice.c154
-rw-r--r--sound/pci/ens1370.c2435
-rw-r--r--sound/pci/ens1371.c2
-rw-r--r--sound/pci/es1938.c1848
-rw-r--r--sound/pci/es1968.c2914
-rw-r--r--sound/pci/fm801.c1462
-rw-r--r--sound/pci/hda/Kconfig259
-rw-r--r--sound/pci/hda/Makefile50
-rw-r--r--sound/pci/hda/ca0132_regs.h396
-rw-r--r--sound/pci/hda/hda_auto_parser.c1047
-rw-r--r--sound/pci/hda/hda_auto_parser.h115
-rw-r--r--sound/pci/hda/hda_beep.c343
-rw-r--r--sound/pci/hda/hda_beep.h47
-rw-r--r--sound/pci/hda/hda_bind.c333
-rw-r--r--sound/pci/hda/hda_codec.c4061
-rw-r--r--sound/pci/hda/hda_controller.c1337
-rw-r--r--sound/pci/hda/hda_controller.h216
-rw-r--r--sound/pci/hda/hda_controller_trace.h99
-rw-r--r--sound/pci/hda/hda_eld.c767
-rw-r--r--sound/pci/hda/hda_generic.c6227
-rw-r--r--sound/pci/hda/hda_generic.h368
-rw-r--r--sound/pci/hda/hda_hwdep.c121
-rw-r--r--sound/pci/hda/hda_intel.c2860
-rw-r--r--sound/pci/hda/hda_intel.h37
-rw-r--r--sound/pci/hda/hda_intel_trace.h54
-rw-r--r--sound/pci/hda/hda_jack.c688
-rw-r--r--sound/pci/hda/hda_jack.h186
-rw-r--r--sound/pci/hda/hda_local.h732
-rw-r--r--sound/pci/hda/hda_proc.c912
-rw-r--r--sound/pci/hda/hda_sysfs.c799
-rw-r--r--sound/pci/hda/hda_tegra.c564
-rw-r--r--sound/pci/hda/hp_x360_helper.c95
-rw-r--r--sound/pci/hda/ideapad_s740_helper.c492
-rw-r--r--sound/pci/hda/patch_analog.c1187
-rw-r--r--sound/pci/hda/patch_ca0110.c88
-rw-r--r--sound/pci/hda/patch_ca0132.c9972
-rw-r--r--sound/pci/hda/patch_cirrus.c1244
-rw-r--r--sound/pci/hda/patch_cmedia.c127
-rw-r--r--sound/pci/hda/patch_conexant.c1164
-rw-r--r--sound/pci/hda/patch_hdmi.c4458
-rw-r--r--sound/pci/hda/patch_realtek.c11613
-rw-r--r--sound/pci/hda/patch_si3054.c304
-rw-r--r--sound/pci/hda/patch_sigmatel.c5174
-rw-r--r--sound/pci/hda/patch_via.c1255
-rw-r--r--sound/pci/hda/thinkpad_helper.c36
-rw-r--r--sound/pci/ice1712/Makefile13
-rw-r--r--sound/pci/ice1712/ak4xxx.c170
-rw-r--r--sound/pci/ice1712/amp.c83
-rw-r--r--sound/pci/ice1712/amp.h34
-rw-r--r--sound/pci/ice1712/aureon.c2267
-rw-r--r--sound/pci/ice1712/aureon.h51
-rw-r--r--sound/pci/ice1712/delta.c920
-rw-r--r--sound/pci/ice1712/delta.h152
-rw-r--r--sound/pci/ice1712/envy24ht.h206
-rw-r--r--sound/pci/ice1712/ews.c1067
-rw-r--r--sound/pci/ice1712/ews.h72
-rw-r--r--sound/pci/ice1712/hoontech.c370
-rw-r--r--sound/pci/ice1712/hoontech.h64
-rw-r--r--sound/pci/ice1712/ice1712.c2826
-rw-r--r--sound/pci/ice1712/ice1712.h525
-rw-r--r--sound/pci/ice1712/ice1724.c2853
-rw-r--r--sound/pci/ice1712/juli.c684
-rw-r--r--sound/pci/ice1712/juli.h11
-rw-r--r--sound/pci/ice1712/maya44.c748
-rw-r--r--sound/pci/ice1712/maya44.h11
-rw-r--r--sound/pci/ice1712/phase.c950
-rw-r--r--sound/pci/ice1712/phase.h39
-rw-r--r--sound/pci/ice1712/pontis.c809
-rw-r--r--sound/pci/ice1712/pontis.h19
-rw-r--r--sound/pci/ice1712/prodigy192.c791
-rw-r--r--sound/pci/ice1712/prodigy192.h20
-rw-r--r--sound/pci/ice1712/prodigy_hifi.c1265
-rw-r--r--sound/pci/ice1712/prodigy_hifi.h24
-rw-r--r--sound/pci/ice1712/psc724.c450
-rw-r--r--sound/pci/ice1712/psc724.h14
-rw-r--r--sound/pci/ice1712/quartet.c1088
-rw-r--r--sound/pci/ice1712/quartet.h11
-rw-r--r--sound/pci/ice1712/revo.c631
-rw-r--r--sound/pci/ice1712/revo.h41
-rw-r--r--sound/pci/ice1712/se.c752
-rw-r--r--sound/pci/ice1712/se.h16
-rw-r--r--sound/pci/ice1712/stac946x.h26
-rw-r--r--sound/pci/ice1712/vt1720_mobo.c124
-rw-r--r--sound/pci/ice1712/vt1720_mobo.h27
-rw-r--r--sound/pci/ice1712/wm8766.c332
-rw-r--r--sound/pci/ice1712/wm8766.h147
-rw-r--r--sound/pci/ice1712/wm8776.c605
-rw-r--r--sound/pci/ice1712/wm8776.h209
-rw-r--r--sound/pci/ice1712/wtm.c632
-rw-r--r--sound/pci/ice1712/wtm.h21
-rw-r--r--sound/pci/intel8x0.c3266
-rw-r--r--sound/pci/intel8x0m.c1298
-rw-r--r--sound/pci/korg1212/Makefile10
-rw-r--r--sound/pci/korg1212/korg1212.c2473
-rw-r--r--sound/pci/lola/Makefile5
-rw-r--r--sound/pci/lola/lola.c762
-rw-r--r--sound/pci/lola/lola.h514
-rw-r--r--sound/pci/lola/lola_clock.c310
-rw-r--r--sound/pci/lola/lola_mixer.c883
-rw-r--r--sound/pci/lola/lola_pcm.c694
-rw-r--r--sound/pci/lola/lola_proc.c203
-rw-r--r--sound/pci/lx6464es/Makefile3
-rw-r--r--sound/pci/lx6464es/lx6464es.c1119
-rw-r--r--sound/pci/lx6464es/lx6464es.h95
-rw-r--r--sound/pci/lx6464es/lx_core.c1177
-rw-r--r--sound/pci/lx6464es/lx_core.h222
-rw-r--r--sound/pci/lx6464es/lx_defs.h361
-rw-r--r--sound/pci/maestro3.c2773
-rw-r--r--sound/pci/mixart/Makefile9
-rw-r--r--sound/pci/mixart/mixart.c1402
-rw-r--r--sound/pci/mixart/mixart.h207
-rw-r--r--sound/pci/mixart/mixart_core.c588
-rw-r--r--sound/pci/mixart/mixart_core.h558
-rw-r--r--sound/pci/mixart/mixart_hwdep.c578
-rw-r--r--sound/pci/mixart/mixart_hwdep.h142
-rw-r--r--sound/pci/mixart/mixart_mixer.c1181
-rw-r--r--sound/pci/mixart/mixart_mixer.h18
-rw-r--r--sound/pci/nm256/Makefile10
-rw-r--r--sound/pci/nm256/nm256.c1757
-rw-r--r--sound/pci/nm256/nm256_coef.c4608
-rw-r--r--sound/pci/oxygen/Makefile11
-rw-r--r--sound/pci/oxygen/ak4396.h45
-rw-r--r--sound/pci/oxygen/cm9780.h64
-rw-r--r--sound/pci/oxygen/cs2000.h84
-rw-r--r--sound/pci/oxygen/cs4245.h111
-rw-r--r--sound/pci/oxygen/cs4362a.h70
-rw-r--r--sound/pci/oxygen/cs4398.h70
-rw-r--r--sound/pci/oxygen/oxygen.c868
-rw-r--r--sound/pci/oxygen/oxygen.h260
-rw-r--r--sound/pci/oxygen/oxygen_io.c280
-rw-r--r--sound/pci/oxygen/oxygen_lib.c816
-rw-r--r--sound/pci/oxygen/oxygen_mixer.c1127
-rw-r--r--sound/pci/oxygen/oxygen_pcm.c783
-rw-r--r--sound/pci/oxygen/oxygen_regs.h459
-rw-r--r--sound/pci/oxygen/pcm1796.h58
-rw-r--r--sound/pci/oxygen/se6x.c150
-rw-r--r--sound/pci/oxygen/virtuoso.c95
-rw-r--r--sound/pci/oxygen/wm8766.h74
-rw-r--r--sound/pci/oxygen/wm8776.h174
-rw-r--r--sound/pci/oxygen/wm8785.h46
-rw-r--r--sound/pci/oxygen/xonar.h55
-rw-r--r--sound/pci/oxygen/xonar_cs43xx.c441
-rw-r--r--sound/pci/oxygen/xonar_dg.c285
-rw-r--r--sound/pci/oxygen/xonar_dg.h57
-rw-r--r--sound/pci/oxygen/xonar_dg_mixer.c467
-rw-r--r--sound/pci/oxygen/xonar_hdmi.c117
-rw-r--r--sound/pci/oxygen/xonar_lib.c123
-rw-r--r--sound/pci/oxygen/xonar_pcm179x.c1355
-rw-r--r--sound/pci/oxygen/xonar_wm87x6.c1333
-rw-r--r--sound/pci/pcxhr/Makefile3
-rw-r--r--sound/pci/pcxhr/pcxhr.c1662
-rw-r--r--sound/pci/pcxhr/pcxhr.h199
-rw-r--r--sound/pci/pcxhr/pcxhr_core.c1333
-rw-r--r--sound/pci/pcxhr/pcxhr_core.h192
-rw-r--r--sound/pci/pcxhr/pcxhr_hwdep.c409
-rw-r--r--sound/pci/pcxhr/pcxhr_hwdep.h27
-rw-r--r--sound/pci/pcxhr/pcxhr_mix22.c855
-rw-r--r--sound/pci/pcxhr/pcxhr_mix22.h47
-rw-r--r--sound/pci/pcxhr/pcxhr_mixer.c1246
-rw-r--r--sound/pci/pcxhr/pcxhr_mixer.h16
-rw-r--r--sound/pci/riptide/Makefile4
-rw-r--r--sound/pci/riptide/riptide.c2197
-rw-r--r--sound/pci/rme32.c1945
-rw-r--r--sound/pci/rme96.c2528
-rw-r--r--sound/pci/rme9652/Makefile14
-rw-r--r--sound/pci/rme9652/hdsp.c5391
-rw-r--r--sound/pci/rme9652/hdspm.c6979
-rw-r--r--sound/pci/rme9652/rme9652.c2634
-rw-r--r--sound/pci/sis7019.c1446
-rw-r--r--sound/pci/sis7019.h330
-rw-r--r--sound/pci/sonicvibes.c1516
-rw-r--r--sound/pci/trident/Makefile10
-rw-r--r--sound/pci/trident/trident.c172
-rw-r--r--sound/pci/trident/trident.h429
-rw-r--r--sound/pci/trident/trident_main.c3901
-rw-r--r--sound/pci/trident/trident_memory.c299
-rw-r--r--sound/pci/via82xx.c2614
-rw-r--r--sound/pci/via82xx_modem.c1204
-rw-r--r--sound/pci/vx222/Makefile9
-rw-r--r--sound/pci/vx222/vx222.c280
-rw-r--r--sound/pci/vx222/vx222.h103
-rw-r--r--sound/pci/vx222/vx222_ops.c1024
-rw-r--r--sound/pci/ymfpci/Makefile10
-rw-r--r--sound/pci/ymfpci/ymfpci.c369
-rw-r--r--sound/pci/ymfpci/ymfpci.h374
-rw-r--r--sound/pci/ymfpci/ymfpci_main.c2447
378 files changed, 282492 insertions, 0 deletions
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
new file mode 100644
index 000000000..41ce12597
--- /dev/null
+++ b/sound/pci/Kconfig
@@ -0,0 +1,914 @@
+# 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
+ 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
+ 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
+ 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"
+ 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 <mjander@users.sourceforge.net>.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-au8810.
+
+config SND_AU8820
+ tristate "Aureal Vortex"
+ 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 <mjander@users.sourceforge.net>.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-au8820.
+
+config SND_AU8830
+ tristate "Aureal Vortex 2"
+ 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 <mjander@users.sourceforge.net>.
+
+ 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
+ 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
+ <file:Documentation/sound/cards/bt87x.rst>.
+
+ 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"
+ 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"
+ 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
+ <file:Documentation/sound/cards/cmipci.rst>.
+
+ 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)"
+ 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"
+ 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"
+ 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
+ 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"
+ 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)"
+ 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
+ help
+ Say Y to include support for Sound Blaster PCI 512, Live!,
+ Audigy and E-mu APS (partially supported) soundcards.
+
+ The confusing multitude of mixer controls is documented in
+ <file:Documentation/sound/cards/sb-live-mixer.rst> and
+ <file:Documentation/sound/cards/audigy-mixer.rst>.
+
+ 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
+ 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"
+ 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"
+ 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
+ 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
+ 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_V4L2=y || VIDEO_V4L2=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"
+ 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_V4L2=y || VIDEO_V4L2=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
+ 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)"
+ 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
+ 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 <file:Documentation/sound/cards/mixart.rst>.
+
+ 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"
+ 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"
+ 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
+ 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
+ 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
+ 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"
+ 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"
+ 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)"
+ 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"
+ 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"
+ 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
+
+source "sound/pci/hda/Kconfig"
diff --git a/sound/pci/Makefile b/sound/pci/Makefile
new file mode 100644
index 000000000..04cac7469
--- /dev/null
+++ b/sound/pci/Makefile
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1889-objs := ad1889.o
+snd-als300-objs := als300.o
+snd-als4000-objs := als4000.o
+snd-atiixp-objs := atiixp.o
+snd-atiixp-modem-objs := atiixp_modem.o
+snd-azt3328-objs := azt3328.o
+snd-bt87x-objs := bt87x.o
+snd-cmipci-objs := cmipci.o
+snd-cs4281-objs := cs4281.o
+snd-cs5530-objs := cs5530.o
+snd-ens1370-objs := ens1370.o ak4531_codec.o
+snd-ens1371-objs := ens1371.o
+snd-es1938-objs := es1938.o
+snd-es1968-objs := es1968.o
+snd-fm801-objs := fm801.o
+snd-intel8x0-objs := intel8x0.o
+snd-intel8x0m-objs := intel8x0m.o
+snd-maestro3-objs := maestro3.o
+snd-rme32-objs := rme32.o
+snd-rme96-objs := rme96.o
+snd-sis7019-objs := sis7019.o
+snd-sonicvibes-objs := sonicvibes.o
+snd-via82xx-objs := via82xx.o
+snd-via82xx-modem-objs := 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/ \
+ hda/ \
+ 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 <perex@perex.cz>
+#
+
+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..d894dcdf3
--- /dev/null
+++ b/sound/pci/ac97/ac97_codec.c
@@ -0,0 +1,2924 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * 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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include "ac97_id.h"
+
+#include "ac97_patch.c"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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
+{ 0x50534304, 0xffffffff, "UCB1400", patch_ucb1400, NULL },
+{ 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;
+ mutex_lock(&ac97->reg_mutex);
+ ac97->regs[reg] = value;
+ ac97->bus->ops->write(ac97, reg, value);
+ set_bit(reg, ac97->reg_accessed);
+ mutex_unlock(&ac97->reg_mutex);
+}
+
+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;
+ mutex_lock(&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);
+ mutex_unlock(&ac97->reg_mutex);
+ 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)
+{
+ int change;
+
+ if (!snd_ac97_valid_reg(ac97, reg))
+ return -EINVAL;
+ mutex_lock(&ac97->reg_mutex);
+ change = snd_ac97_update_bits_nolock(ac97, reg, mask, value);
+ mutex_unlock(&ac97->reg_mutex);
+ return change;
+}
+
+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;
+
+ mutex_lock(&ac97->page_mutex);
+ old = ac97->spec.ad18xx.pcmreg[codec];
+ new = (old & ~mask) | (value & mask);
+ change = old != new;
+ if (change) {
+ mutex_lock(&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);
+ mutex_unlock(&ac97->reg_mutex);
+ }
+ mutex_unlock(&ac97->page_mutex);
+ 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);
+
+ mutex_lock(&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;
+ mutex_unlock(&ac97->reg_mutex);
+ 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;
+ }
+ }
+
+ mutex_lock(&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 */
+ }
+ }
+ mutex_unlock(&ac97->reg_mutex);
+
+ 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);
+
+ mutex_lock(&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 */
+ }
+ mutex_unlock(&ac97->reg_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ac97->page_mutex);
+ 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);
+ if ((err = snd_ac97_cmute_new_stereo(card, name, reg,
+ check_stereo, check_amix,
+ ac97)) < 0)
+ return err;
+ }
+ check_volume_resolution(ac97, reg, &lo_max, &hi_max);
+ if (lo_max) {
+ sprintf(name, "%s Volume", pfx);
+ if ((err = snd_ac97_cvol_new(card, name, reg, lo_max, hi_max, ac97)) < 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)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_center[0], ac97))) < 0)
+ return err;
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_center[1], ac97))) < 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)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_lfe[0], ac97))) < 0)
+ return err;
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_lfe[1], ac97))) < 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 */
+ if ((err = snd_ac97_cmix_new_stereo(card, "Surround Playback",
+ AC97_SURROUND_MASTER, 1, 0,
+ ac97)) < 0)
+ return err;
+ }
+
+ /* build headphone controls */
+ if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) {
+ if ((err = snd_ac97_cmix_new(card, "Headphone Playback",
+ AC97_HEADPHONE, 0, ac97)) < 0)
+ return err;
+ }
+
+ /* build master mono controls */
+ if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_MONO)) {
+ if ((err = snd_ac97_cmix_new(card, "Master Mono Playback",
+ AC97_MASTER_MONO, 0, ac97)) < 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++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_tone[idx], ac97))) < 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++)
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_pc_beep[idx], ac97))) < 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)) {
+ if ((err = snd_ac97_cmix_new(card, "Phone Playback",
+ AC97_PHONE, 1, ac97)) < 0)
+ return err;
+ }
+ }
+
+ /* build MIC controls */
+ if (!(ac97->flags & AC97_HAS_NO_MIC)) {
+ if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) {
+ if ((err = snd_ac97_cmix_new(card, "Mic Playback",
+ AC97_MIC, 1, ac97)) < 0)
+ return err;
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_mic_boost, ac97))) < 0)
+ return err;
+ }
+ }
+
+ /* build Line controls */
+ if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) {
+ if ((err = snd_ac97_cmix_new(card, "Line Playback",
+ AC97_LINE, 1, ac97)) < 0)
+ return err;
+ }
+
+ /* build CD controls */
+ if (!(ac97->flags & AC97_HAS_NO_CD)) {
+ if (snd_ac97_try_volume_mix(ac97, AC97_CD)) {
+ if ((err = snd_ac97_cmix_new(card, "CD Playback",
+ AC97_CD, 1, ac97)) < 0)
+ return err;
+ }
+ }
+
+ /* build Video controls */
+ if (!(ac97->flags & AC97_HAS_NO_VIDEO)) {
+ if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) {
+ if ((err = snd_ac97_cmix_new(card, "Video Playback",
+ AC97_VIDEO, 1, ac97)) < 0)
+ return err;
+ }
+ }
+
+ /* build Aux controls */
+ if (!(ac97->flags & AC97_HAS_NO_AUX)) {
+ if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) {
+ if ((err = snd_ac97_cmix_new(card, "Aux Playback",
+ AC97_AUX, 1, ac97)) < 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++)
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_pcm[idx], ac97))) < 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++)
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_surround[idx], ac97))) < 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++)
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_center[idx], ac97))) < 0)
+ return err;
+ set_tlv_db_scale(kctl, db_scale_5bit);
+ for (idx = 0; idx < 2; idx++)
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_lfe[idx], ac97))) < 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)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97))) < 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;
+ }
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97))) < 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++)
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_mic_capture[idx], ac97))) < 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)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_PCM_OUT], ac97))) < 0)
+ return err;
+ }
+
+ /* build Simulated Stereo Enhancement control */
+ if (ac97->caps & AC97_BC_SIM_STEREO) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_STEREO_ENHANCEMENT], ac97))) < 0)
+ return err;
+ }
+
+ /* build 3D Stereo Enhancement control */
+ if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 13)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_3D], ac97))) < 0)
+ return err;
+ }
+
+ /* build Loudness control */
+ if (ac97->caps & AC97_BC_LOUDNESS) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOUDNESS], ac97))) < 0)
+ return err;
+ }
+
+ /* build Mono output select control */
+ if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 9)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MONO], ac97))) < 0)
+ return err;
+ }
+
+ /* build Mic select control */
+ if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 8)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MIC], ac97))) < 0)
+ return err;
+ }
+
+ /* build ADC/DAC loopback control */
+ if (enable_loopback && snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 7)) {
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOOPBACK], ac97))) < 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;
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ if (val)
+ kctl->private_value = AC97_3D_CONTROL | (9 << 8) | (7 << 16);
+ if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[1], ac97))) < 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) {
+ if ((err = ac97->build_ops->build_spdif(ac97)) < 0)
+ return err;
+ } else {
+ for (idx = 0; idx < 5; idx++)
+ if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_spdif[idx], ac97))) < 0)
+ return err;
+ if (ac97->build_ops->build_post_spdif) {
+ if ((err = ac97->build_ops->build_post_spdif(ac97)) < 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)
+ if ((err = ac97->build_ops->build_specific(ac97)) < 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);
+ if ((err = snd_ctl_add(card, kctl)) < 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++)
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ac97_controls_modem_switches[idx], ac97))) < 0)
+ return err;
+
+ /* build chip specific controls */
+ if (ac97->build_ops->build_specific)
+ if ((err = ac97->build_ops->build_specific(ac97)) < 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, 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;
+
+ strcpy(name, pid->name);
+ 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) {
+ strcat(name, " ");
+ strcat(name, pid->name);
+ if (pid->mask != 0xffffffff)
+ sprintf(name + strlen(name), " rev %d", id & ~pid->mask);
+ if (ac97 && pid->patch) {
+ if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
+ (! modem && ! (pid->flags & AC97_MODEM_PATCH)))
+ pid->patch(ac97);
+ }
+ } else
+ sprintf(name + strlen(name), " 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(sizeof(*bus), GFP_KERNEL);
+ 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);
+ if ((err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)) < 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));
+ if ((err = device_register(&ac97->dev)) < 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(sizeof(*ac97), GFP_KERNEL);
+ 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);
+ if (((err = snd_ac97_read(ac97, AC97_REC_GAIN)) & 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, !ac97_is_audio(ac97));
+ snd_ac97_get_name(NULL, ac97->id, 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') {
+ strcpy(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);
+ if ((err = snd_component_add(card, comp)) < 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') {
+ strcpy(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);
+ if ((err = snd_component_add(card, comp)) < 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);
+ if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ac97, &ops)) < 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,
+ msecs_to_jiffies(power_save * 1000));
+ 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)
+{
+ if (suffix)
+ sprintf(dst, "%s %s", src, suffix);
+ else
+ strcpy(dst, src);
+}
+
+/* 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);
+ if (kctl) {
+ set_ctl_name(kctl->id.name, dst, suffix);
+ 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;
+ kctl1 = ctl_find(ac97, s1, suffix);
+ kctl2 = ctl_find(ac97, s2, suffix);
+ if (kctl1 && kctl2) {
+ set_ctl_name(kctl1->id.name, s2, suffix);
+ set_ctl_name(kctl2->id.name, s1, suffix);
+ 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 <perex@perex.cz>
+ * 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..8eeae2dec
--- /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 <perex@perex.cz>
+ * 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,
+ 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..1627a74ba
--- /dev/null
+++ b/sound/pci/ac97/ac97_patch.c
@@ -0,0 +1,3932 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * 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++)
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&controls[idx], ac97))) < 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_ctl_elem_id sid;
+ struct snd_kcontrol *kctl;
+ memset(&sid, 0, sizeof(sid));
+ strcpy(sid.name, name);
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ kctl = snd_ctl_find_id(ac97->bus->card, &sid);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ac97->page_mutex); /* unlock paging */
+ 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 <maehara@debian.org> */
+
+/* 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;
+ strcpy(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;
+
+ if ((err = patch_build_controls(ac97, snd_ac97_ymf753_controls_spdif, ARRAY_SIZE(snd_ac97_ymf753_controls_spdif))) < 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 <lrg@slimlogic.co.uk>
+ * 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 <bentson@holmsjoen.com>
+ * WM9703/9707/9708/9717
+ */
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm97xx_snd_ac97_controls); i++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm97xx_snd_ac97_controls[i], ac97))) < 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++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9704_snd_ac97_controls[i], ac97))) < 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++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9711_snd_ac97_controls[i], ac97))) < 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++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_3d[i], ac97))) < 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++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls[i], ac97))) < 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;
+
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(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;
+
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
+ kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 0, 3, 0);
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(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))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[0], 1)) < 0)
+ return err;
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 0))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[1], 1)) < 0)
+ return err;
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 2))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_4speaker, 1)) < 0)
+ return err;
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 3))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_phaseinvert, 1)) < 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ac97->page_mutex);
+ 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");
+ if ((err = patch_build_controls(ac97, &snd_ac97_stac9708_bias_control, 1)) < 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 */
+ if ((err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3)) < 0)
+ return err;
+ /* switch, spsa */
+ if ((err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[0], 1)) < 0)
+ return err;
+ switch (ac97->id & AC97_ID_CS_MASK) {
+ case AC97_ID_CS4205:
+ if ((err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[1], 1)) < 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 */
+ if ((err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3)) < 0)
+ return err;
+ /* switch */
+ if ((err = patch_build_controls(ac97, &snd_ac97_conexant_controls_spdif[0], 1)) < 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;
+
+ if ((err = patch_build_controls(ac97, snd_ac97_controls_ad1885, ARRAY_SIZE(snd_ac97_controls_ad1885))) < 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;
+
+ if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 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;
+
+ if ((err = patch_ad1888_specific(ac97)) < 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");
+
+ if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 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;
+
+ if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 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;
+
+ if ((err = patch_build_controls(ac97, snd_ac97_controls_alc650, ARRAY_SIZE(snd_ac97_controls_alc650))) < 0)
+ return err;
+ if (ac97->ext_id & AC97_EI_SPDIF) {
+ if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc650, ARRAY_SIZE(snd_ac97_spdif_controls_alc650))) < 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;
+
+ if ((err = patch_build_controls(ac97, snd_ac97_controls_alc655, ARRAY_SIZE(snd_ac97_controls_alc655))) < 0)
+ return err;
+ if (ac97->ext_id & AC97_EI_SPDIF) {
+ if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655))) < 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;
+
+ if ((err = patch_build_controls(ac97, snd_ac97_controls_alc850, ARRAY_SIZE(snd_ac97_controls_alc850))) < 0)
+ return err;
+ if (ac97->ext_id & AC97_EI_SPDIF) {
+ if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655))) < 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)
+{
+ struct snd_ctl_elem_id id;
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(id.name, name);
+ return snd_ctl_find_id(ac97->bus->card, &id);
+}
+
+/* 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;
+ const char * const *s;
+ 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;
+
+ for (s = followers; *s; s++) {
+ struct snd_kcontrol *sctl;
+
+ sctl = snd_ac97_find_mixer_ctl(ac97, *s);
+ if (!sctl) {
+ dev_dbg(ac97->bus->card->dev,
+ "Cannot find follower %s, skipped\n", *s);
+ continue;
+ }
+ err = snd_ctl_add_follower(kctl, sctl);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int patch_vt1616_specific(struct snd_ac97 * ac97)
+{
+ struct snd_kcontrol *kctl;
+ int err;
+
+ if (snd_ac97_try_bit(ac97, 0x5a, 9))
+ if ((err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[0], 1)) < 0)
+ return err;
+ if ((err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[1], ARRAY_SIZE(snd_ac97_controls_vt1616) - 1)) < 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);
+
+ mutex_lock(&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);
+ mutex_unlock(&pac97->page_mutex);
+
+ 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;
+ if ((err = patch_build_controls(ac97, snd_ac97_controls_it2646, ARRAY_SIZE(snd_ac97_controls_it2646))) < 0)
+ return err;
+ if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_it2646, ARRAY_SIZE(snd_ac97_spdif_controls_it2646))) < 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++)
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_si3036[idx], ac97))) < 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;
+}
+
+/*
+ * UCB1400 codec (http://www.semiconductors.philips.com/acrobat_download/datasheets/UCB1400-02.pdf)
+ */
+static const struct snd_kcontrol_new snd_ac97_controls_ucb1400[] = {
+/* enable/disable headphone driver which allows direct connection to
+ stereo headphone without the use of external DC blocking
+ capacitors */
+AC97_SINGLE("Headphone Driver", 0x6a, 6, 1, 0),
+/* Filter used to compensate the DC offset is added in the ADC to remove idle
+ tones from the audio band. */
+AC97_SINGLE("DC Filter", 0x6a, 4, 1, 0),
+/* Control smart-low-power mode feature. Allows automatic power down
+ of unused blocks in the ADC analog front end and the PLL. */
+AC97_SINGLE("Smart Low Power Mode", 0x6c, 4, 3, 0),
+};
+
+static int patch_ucb1400_specific(struct snd_ac97 * ac97)
+{
+ int idx, err;
+ for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_ucb1400); idx++)
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_ucb1400[idx], ac97))) < 0)
+ return err;
+ return 0;
+}
+
+static const struct snd_ac97_build_ops patch_ucb1400_ops = {
+ .build_specific = patch_ucb1400_specific,
+};
+
+static int patch_ucb1400(struct snd_ac97 * ac97)
+{
+ ac97->build_ops = &patch_ucb1400_ops;
+ /* enable headphone driver and smart low power mode by default */
+ snd_ac97_write_cache(ac97, 0x6a, 0x0050);
+ snd_ac97_write_cache(ac97, 0x6c, 0x0030);
+ 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 <perex@perex.cz>
+ * 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..491de1a62
--- /dev/null
+++ b/sound/pci/ac97/ac97_pcm.c
@@ -0,0 +1,737 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * 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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/export.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#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;
+ }
+
+ mutex_lock(&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);
+ mutex_unlock(&ac97->reg_mutex);
+ 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_PMC_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<<AC97_EI_DACS_SLOT_SHIFT); break;
+ case 3: es |= (2<<AC97_EI_DACS_SLOT_SHIFT); break;
+ }
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_ID, es);
+ }
+ switch (ac97->addr) {
+ case 0:
+ slots |= (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
+ if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+ slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
+ if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+ slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+ if (ac97->ext_id & AC97_EI_SPDIF) {
+ if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
+ else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
+ else
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+ }
+ *rate_table = 0;
+ break;
+ case 1:
+ case 2:
+ slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
+ if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+ slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+ if (ac97->ext_id & AC97_EI_SPDIF) {
+ if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
+ else
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+ }
+ *rate_table = 1;
+ break;
+ case 3:
+ slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+ if (ac97->ext_id & AC97_EI_SPDIF)
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+ *rate_table = 2;
+ break;
+ }
+ return slots;
+ } else {
+ unsigned short slots;
+ slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
+ if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+ slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
+ if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+ slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+ if (ac97->ext_id & AC97_EI_SPDIF) {
+ if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
+ else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
+ else
+ *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+ }
+ *rate_table = 0;
+ return slots;
+ }
+}
+
+static unsigned short get_cslots(struct snd_ac97 *ac97)
+{
+ unsigned short slots;
+
+ if (!ac97_is_audio(ac97))
+ return 0;
+ slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
+ slots |= (1<<AC97_SLOT_MIC);
+ return slots;
+}
+
+static unsigned int get_rates(struct ac97_pcm *pcm, unsigned int cidx, unsigned short slots, int dbl)
+{
+ int i, idx;
+ unsigned int rates = ~0;
+ unsigned char reg;
+
+ for (i = 3; i < 12; i++) {
+ if (!(slots & (1 << i)))
+ continue;
+ reg = get_slot_reg(pcm, cidx, i, dbl);
+ switch (reg) {
+ case AC97_PCM_FRONT_DAC_RATE: idx = AC97_RATES_FRONT_DAC; break;
+ case AC97_PCM_SURR_DAC_RATE: idx = AC97_RATES_SURR_DAC; break;
+ case AC97_PCM_LFE_DAC_RATE: idx = AC97_RATES_LFE_DAC; break;
+ case AC97_PCM_LR_ADC_RATE: idx = AC97_RATES_ADC; break;
+ case AC97_PCM_MIC_ADC_RATE: idx = AC97_RATES_MIC_ADC; break;
+ default: idx = AC97_RATES_SPDIF; break;
+ }
+ rates &= pcm->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 = kcalloc(pcms_count, sizeof(struct ac97_pcm), GFP_KERNEL);
+ 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<<AC97_SLOT_PCM_LEFT) | (1<<AC97_SLOT_PCM_RIGHT) |
+ (1<<AC97_SLOT_PCM_LEFT_0) | (1<<AC97_SLOT_PCM_RIGHT_0);
+ if ((tmp & pcm->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;
+ }
+ }
+ spin_lock_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)) {
+ spin_unlock_irq(&pcm->bus->bus_lock);
+ 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) {
+ spin_unlock_irq(&pcm->bus->bus_lock);
+ dev_err(bus->card->dev,
+ "cannot find configuration for AC97 slot %i\n",
+ i);
+ err = -EAGAIN;
+ goto error;
+ }
+ }
+ pcm->cur_dbl = r;
+ spin_unlock_irq(&pcm->bus->bus_lock);
+ 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;
+ spin_lock_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;
+ spin_unlock_irq(&pcm->bus->bus_lock);
+ 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..5426f7bc9
--- /dev/null
+++ b/sound/pci/ac97/ac97_proc.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * 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 <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#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, 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",
+ val & 0x4000 ? "on" : "off",
+ val & 0x2000 ? "on" : "off",
+ val & 0x1000 ? "on" : "off",
+ val & 0x0200 ? "Mic" : "MIX",
+ val & 0x0100 ? "Mic2" : "Mic1",
+ val & 0x0080 ? "on" : "off");
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&ac97->page_mutex);
+}
+
+#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;
+ mutex_lock(&ac97->page_mutex);
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ /* register must be even */
+ if (reg < 0x80 && (reg & 1) == 0 && val <= 0xffff)
+ snd_ac97_write_cache(ac97, reg, val);
+ }
+ mutex_unlock(&ac97->page_mutex);
+}
+#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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&ac97->page_mutex);
+}
+
+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..5d42c4249
--- /dev/null
+++ b/sound/pci/ad1889.c
@@ -0,0 +1,1010 @@
+// 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 <kyle@parisc-linux.org>
+ * Copyright (C) 2005, Thibaut Varene <varenet@parisc-linux.org>
+ * Based on the OSS AD1889 driver by Randolph Chung <tausq@debian.org>
+ *
+ * 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 <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+
+#include "ad1889.h"
+#include "ac97/ac97_id.h"
+
+#define AD1889_DRVVER "Version: 1.7"
+
+MODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>, Thibaut Varene <t-bone@parisc-linux.org>");
+MODULE_DESCRIPTION("Analog Devices AD1889 ALSA sound driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Analog Devices,AD1889}}");
+
+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 */
+ spin_lock_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);
+
+ spin_unlock_irq(&chip->lock);
+
+ 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 */
+ spin_lock_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);
+
+ spin_unlock_irq(&chip->lock);
+
+ 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;
+ strcpy(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",
+ (reg & AD_DS_WSMC_WAEN) ? "enabled" : "disabled");
+ 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",
+ reg & AD_DS_WSMC_SYEN ? "enabled" : "disabled");
+
+ /* 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",
+ (reg & AD_DS_RAMC_ADEN) ? "enabled" : "disabled");
+ 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",
+ reg & AD_DS_RAMC_REEN ? "enabled" : "disabled");
+
+ /* 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 void
+snd_ad1889_ac97_bus_free(struct snd_ac97_bus *bus)
+{
+ struct snd_ad1889 *chip = bus->private_data;
+ chip->ac97_bus = NULL;
+}
+
+static void
+snd_ad1889_ac97_free(struct snd_ac97 *ac97)
+{
+ struct snd_ad1889 *chip = ac97->private_data;
+ chip->ac97 = NULL;
+}
+
+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;
+
+ chip->ac97_bus->private_free = snd_ad1889_ac97_bus_free;
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = chip;
+ ac97.private_free = snd_ad1889_ac97_free;
+ 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 int
+snd_ad1889_free(struct snd_ad1889 *chip)
+{
+ if (chip->irq < 0)
+ goto skip_hw;
+
+ spin_lock_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! */
+
+ spin_unlock_irq(&chip->lock);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+skip_hw:
+ iounmap(chip->iobase);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int
+snd_ad1889_dev_free(struct snd_device *device)
+{
+ struct snd_ad1889 *chip = device->device_data;
+ return snd_ad1889_free(chip);
+}
+
+static int
+snd_ad1889_init(struct snd_ad1889 *chip)
+{
+ 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_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct snd_ad1889 **rchip)
+{
+ int err;
+
+ struct snd_ad1889 *chip;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_ad1889_dev_free,
+ };
+
+ *rchip = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ /* check PCI availability (32bit DMA) */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) {
+ dev_err(card->dev, "error setting 32-bit DMA mask.\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ /* allocate chip specific data with zero-filled memory */
+ if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+ card->private_data = chip;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ /* (1) PCI resource allocation */
+ if ((err = pci_request_regions(pci, card->driver)) < 0)
+ goto free_and_ret;
+
+ chip->bar = pci_resource_start(pci, 0);
+ chip->iobase = pci_ioremap_bar(pci, 0);
+ if (chip->iobase == NULL) {
+ dev_err(card->dev, "unable to reserve region.\n");
+ err = -EBUSY;
+ goto free_and_ret;
+ }
+
+ pci_set_master(pci);
+
+ spin_lock_init(&chip->lock); /* only now can we call ad1889_free */
+
+ if (request_irq(pci->irq, snd_ad1889_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "cannot obtain IRQ %d\n", pci->irq);
+ snd_ad1889_free(chip);
+ return -EBUSY;
+ }
+
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ /* (2) initialization of the chip hardware */
+ if ((err = snd_ad1889_init(chip)) < 0) {
+ snd_ad1889_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_ad1889_free(chip);
+ return err;
+ }
+
+ *rchip = chip;
+
+ return 0;
+
+free_and_ret:
+ kfree(chip);
+ pci_disable_device(pci);
+
+ return err;
+}
+
+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_card_new(&pci->dev, index[devno], id[devno], THIS_MODULE,
+ 0, &card);
+ /* XXX REVISIT: we can probably allocate chip in this call */
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, "AD1889");
+ strcpy(card->shortname, "Analog Devices AD1889");
+
+ /* (3) */
+ err = snd_ad1889_create(card, pci, &chip);
+ if (err < 0)
+ goto free_and_ret;
+
+ /* (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)
+ goto free_and_ret;
+
+ err = snd_ad1889_pcm_init(chip, 0);
+ if (err < 0)
+ goto free_and_ret;
+
+ /* register proc interface */
+ snd_ad1889_proc_init(chip);
+
+ /* (6) */
+ err = snd_card_register(card);
+ if (err < 0)
+ goto free_and_ret;
+
+ /* (7) */
+ pci_set_drvdata(pci, card);
+
+ devno++;
+ return 0;
+
+free_and_ret:
+ snd_card_free(card);
+ return err;
+}
+
+static void
+snd_ad1889_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+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,
+ .remove = snd_ad1889_remove,
+};
+
+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 <kyle@parisc-linux.org>
+ */
+
+#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..e0a81f99f
--- /dev/null
+++ b/sound/pci/ak4531_codec.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Universal routines for AK4531 codec
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/ak4531_codec.h>
+#include <sound/tlv.h>
+
+/*
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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;
+
+ mutex_lock(&ak4531->reg_mutex);
+ val = (ak4531->regs[reg] >> shift) & mask;
+ mutex_unlock(&ak4531->reg_mutex);
+ 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;
+ mutex_lock(&ak4531->reg_mutex);
+ val = (ak4531->regs[reg] & ~(mask << shift)) | val;
+ change = val != ak4531->regs[reg];
+ ak4531->write(ak4531, reg, ak4531->regs[reg] = val);
+ mutex_unlock(&ak4531->reg_mutex);
+ 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;
+
+ mutex_lock(&ak4531->reg_mutex);
+ left = (ak4531->regs[left_reg] >> left_shift) & mask;
+ right = (ak4531->regs[right_reg] >> right_shift) & mask;
+ mutex_unlock(&ak4531->reg_mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ mutex_unlock(&ak4531->reg_mutex);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&ak4531->reg_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ak4531->reg_mutex);
+ 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(sizeof(*ak4531), GFP_KERNEL);
+ if (ak4531 == NULL)
+ return -ENOMEM;
+ *ak4531 = *_ak4531;
+ mutex_init(&ak4531->reg_mutex);
+ if ((err = snd_component_add(card, "AK4531")) < 0) {
+ snd_ak4531_free(ak4531);
+ return err;
+ }
+ strcpy(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++) {
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ak4531_controls[idx], ak4531))) < 0) {
+ snd_ak4531_free(ak4531);
+ return err;
+ }
+ }
+ snd_ak4531_proc_init(card, ak4531);
+ if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ak4531, &ops)) < 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..8156198fb
--- /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 <perex@perex.cz>
+#
+
+snd-ali5451-objs := 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..4462375d2
--- /dev/null
+++ b/sound/pci/ali5451/ali5451.c
@@ -0,0 +1,2232 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Matt Wu <Matt_Wu@acersoftech.com.cn>
+ * Apr 26, 2001
+ * Routines for control of ALi pci audio M5451
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Matt Wu <Matt_Wu@acersoftech.com.cn>");
+MODULE_DESCRIPTION("ALI M5451");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALI,M5451,pci},{ALI,M5451}}");
+
+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;
+
+#ifdef CONFIG_PM_SLEEP
+ struct snd_ali_image *image;
+#endif
+};
+
+static const struct pci_device_id snd_ali_ids[] = {
+ {PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M5451), 0, 0, 0},
+ {0, }
+};
+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);
+
+ spin_lock_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");
+ spin_unlock_irq(&codec->voice_alloc);
+ return NULL;
+ }
+ pvoice = &(codec->synth.voices[idx]);
+ pvoice->codec = codec;
+ pvoice->use = 1;
+ pvoice->pcm = 1;
+ pvoice->mode = rec;
+ spin_unlock_irq(&codec->voice_alloc);
+ return pvoice;
+ }
+ spin_unlock_irq(&codec->voice_alloc);
+ 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);
+ spin_lock_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;
+ spin_unlock_irq(&codec->voice_alloc);
+ 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);
+ }
+ }
+ spin_lock(&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);
+ spin_unlock(&codec->reg_lock);
+
+ 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");
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&codec->reg_lock);
+ 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;
+
+ spin_lock(&codec->reg_lock);
+ if (!pvoice->running) {
+ spin_unlock(&codec->reg_lock);
+ return 0;
+ }
+ outb(pvoice->number, ALI_REG(codec, ALI_GC_CIR));
+ cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2));
+ spin_unlock(&codec->reg_lock);
+ 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;
+
+ spin_lock(&codec->reg_lock);
+ if (!pvoice->running) {
+ spin_unlock(&codec->reg_lock);
+ return 0;
+ }
+ outb(pvoice->number, ALI_REG(codec, ALI_GC_CIR));
+ cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2));
+ spin_unlock(&codec->reg_lock);
+
+ 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;
+ strcpy(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 = kcontrol->private_data;
+ unsigned int spdif_enable;
+
+ spdif_enable = ucontrol->value.integer.value[0] ? 1 : 0;
+
+ spin_lock_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;
+ spin_unlock_irq(&codec->reg_lock);
+ return 0;
+}
+
+static int snd_ali5451_spdif_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ali *codec = kcontrol->private_data;
+ unsigned int change = 0, spdif_enable = 0;
+
+ spdif_enable = ucontrol->value.integer.value[0] ? 1 : 0;
+
+ spin_lock_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;
+ }
+ spin_unlock_irq(&codec->reg_lock);
+
+ 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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;
+ int i, j;
+
+ im = chip->image;
+ if (!im)
+ return 0;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ for (i = 0; i < chip->num_of_codecs; i++)
+ snd_ac97_suspend(chip->ac97[i]);
+
+ spin_lock_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));
+
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ int i, j;
+
+ im = chip->image;
+ if (!im)
+ return 0;
+
+ spin_lock_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));
+
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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 SIMPLE_DEV_PM_OPS(ali_pm, ali_suspend, ali_resume);
+#define ALI_PM_OPS &ali_pm
+#else
+#define ALI_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static int snd_ali_free(struct snd_ali * codec)
+{
+ if (codec->hw_initialized)
+ snd_ali_disable_address_interrupt(codec);
+ if (codec->irq >= 0)
+ free_irq(codec->irq, codec);
+ if (codec->port)
+ pci_release_regions(codec->pci);
+ pci_disable_device(codec->pci);
+#ifdef CONFIG_PM_SLEEP
+ kfree(codec->image);
+#endif
+ pci_dev_put(codec->pci_m1533);
+ pci_dev_put(codec->pci_m7101);
+ kfree(codec);
+ return 0;
+}
+
+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 = pci_request_regions(codec->pci, "ALI 5451");
+ if (err < 0)
+ return err;
+ codec->port = pci_resource_start(codec->pci, 0);
+
+ if (request_irq(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_dev_free(struct snd_device *device)
+{
+ struct snd_ali *codec = device->device_data;
+ snd_ali_free(codec);
+ return 0;
+}
+
+static int snd_ali_create(struct snd_card *card,
+ struct pci_dev *pci,
+ int pcm_streams,
+ int spdif_support,
+ struct snd_ali **r_ali)
+{
+ struct snd_ali *codec;
+ int i, err;
+ unsigned short cmdw;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_ali_dev_free,
+ };
+
+ *r_ali = NULL;
+
+ dev_dbg(card->dev, "creating ...\n");
+
+ /* enable PCI device */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 31 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(31)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(31)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 31bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ codec = kzalloc(sizeof(*codec), GFP_KERNEL);
+ if (!codec) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ 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);
+ }
+ pci_set_master(pci);
+
+ if (snd_ali_resources(codec)) {
+ snd_ali_free(codec);
+ return -EBUSY;
+ }
+
+ 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");
+ snd_ali_free(codec);
+ 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");
+ snd_ali_free(codec);
+ return -ENODEV;
+ }
+
+ dev_dbg(card->dev, "snd_device_new is called.\n");
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, codec, &ops);
+ if (err < 0) {
+ snd_ali_free(codec);
+ return err;
+ }
+
+ /* 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;
+ }
+
+#ifdef CONFIG_PM_SLEEP
+ codec->image = kmalloc(sizeof(*codec->image), GFP_KERNEL);
+ if (!codec->image)
+ dev_warn(card->dev, "can't allocate apm buffer\n");
+#endif
+
+ snd_ali_enable_address_interrupt(codec);
+ codec->hw_initialized = 1;
+
+ *r_ali = codec;
+ dev_dbg(card->dev, "created.\n");
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ err = snd_ali_create(card, pci, pcm_channels, spdif, &codec);
+ if (err < 0)
+ goto error;
+ card->private_data = codec;
+
+ dev_dbg(&pci->dev, "mixer building ...\n");
+ err = snd_ali_mixer(codec);
+ if (err < 0)
+ goto error;
+
+ dev_dbg(&pci->dev, "pcm building ...\n");
+ err = snd_ali_build_pcms(codec);
+ if (err < 0)
+ goto error;
+
+ snd_ali_proc_init(codec);
+
+ strcpy(card->driver, "ALI5451");
+ strcpy(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)
+ goto error;
+
+ pci_set_drvdata(pci, card);
+ return 0;
+
+ error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_ali_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver ali5451_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_ali_ids,
+ .probe = snd_ali_probe,
+ .remove = snd_ali_remove,
+ .driver = {
+ .pm = ALI_PM_OPS,
+ },
+};
+
+module_pci_driver(ali5451_driver);
diff --git a/sound/pci/als300.c b/sound/pci/als300.c
new file mode 100644
index 000000000..8d2471ea0
--- /dev/null
+++ b/sound/pci/als300.c
@@ -0,0 +1,782 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * als300.c - driver for Avance Logic ALS300/ALS300+ soundcards.
+ * Copyright (C) 2005 by Ash Willis <ashwillis@programmer.net>
+ *
+ * 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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include <sound/opl3.h>
+
+/* 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 <ashwillis@programmer.net>");
+MODULE_DESCRIPTION("Avance Logic ALS300");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS300},{Avance Logic,ALS300+}}");
+
+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[] = {
+ { 0x4005, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300 },
+ { 0x4005, 0x0308, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300_PLUS },
+ { 0, }
+};
+
+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 int snd_als300_free(struct snd_als300 *chip)
+{
+ snd_als300_set_irq_flag(chip, IRQ_DISABLE);
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_als300_dev_free(struct snd_device *device)
+{
+ struct snd_als300 *chip = device->device_data;
+ return snd_als300_free(chip);
+}
+
+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 void snd_als300_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+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,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus)) < 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(sizeof(*data),
+ GFP_KERNEL);
+
+ 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(sizeof(*data),
+ GFP_KERNEL);
+
+ 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);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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);
+
+ spin_lock(&chip->reg_lock);
+ current_ptr = (u16) snd_als300_gcr_read(chip->port,
+ data->block_counter_register) + 4;
+ spin_unlock(&chip->reg_lock);
+ 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;
+ strcpy(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)
+{
+ unsigned long flags;
+ u32 tmp;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int snd_als300_create(struct snd_card *card,
+ struct pci_dev *pci, int chip_type,
+ struct snd_als300 **rchip)
+{
+ struct snd_als300 *chip;
+ void *irq_handler;
+ int err;
+
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_als300_dev_free,
+ };
+ *rchip = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(28)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(28)) < 0) {
+ dev_err(card->dev, "error setting 28bit DMA mask\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+ pci_set_master(pci);
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+ chip->chip_type = chip_type;
+ spin_lock_init(&chip->reg_lock);
+
+ if ((err = pci_request_regions(pci, "ALS300")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ 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 (request_irq(pci->irq, irq_handler, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_als300_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ snd_als300_init(chip);
+
+ err = snd_als300_ac97(chip);
+ if (err < 0) {
+ dev_err(card->dev, "Could not create ac97\n");
+ snd_als300_free(chip);
+ return err;
+ }
+
+ if ((err = snd_als300_new_pcm(chip)) < 0) {
+ dev_err(card->dev, "Could not create PCM\n");
+ snd_als300_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+ chip, &ops)) < 0) {
+ snd_als300_free(chip);
+ return err;
+ }
+
+ *rchip = chip;
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(snd_als300_pm, snd_als300_suspend, snd_als300_resume);
+#define SND_ALS300_PM_OPS &snd_als300_pm
+#else
+#define SND_ALS300_PM_OPS NULL
+#endif
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+
+ if (err < 0)
+ return err;
+
+ chip_type = pci_id->driver_data;
+
+ if ((err = snd_als300_create(card, pci, chip_type, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ strcpy(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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static struct pci_driver als300_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_als300_ids,
+ .probe = snd_als300_probe,
+ .remove = snd_als300_remove,
+ .driver = {
+ .pm = SND_ALS300_PM_OPS,
+ },
+};
+
+module_pci_driver(als300_driver);
diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c
new file mode 100644
index 000000000..ba6390e9a
--- /dev/null
+++ b/sound/pci/als4000.c
@@ -0,0 +1,1002 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * card-als4000.c - driver for Avance Logic ALS4000 based soundcards.
+ * Copyright (C) 2000 by Bart Hartgers <bart@etpmod.phys.tue.nl>,
+ * Jaroslav Kysela <perex@perex.cz>
+ * Copyright (C) 2002, 2008 by Andreas Mohr <hw7oshyuv3001@sneakemail.com>
+ *
+ * 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 <linux/io.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Bart Hartgers <bart@etpmod.phys.tue.nl>, Andreas Mohr");
+MODULE_DESCRIPTION("Avance Logic ALS4000");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS4000}}");
+
+#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[] = {
+ { 0x4005, 0x4000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, /* ALS4000 */
+ { 0, }
+};
+
+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--;
+
+ spin_lock_irq(&chip->reg_lock);
+ snd_als4000_set_rate(chip, runtime->rate);
+ snd_als4000_set_capture_dma(chip, runtime->dma_addr, size);
+ spin_unlock_irq(&chip->reg_lock);
+ spin_lock_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);
+ spin_unlock_irq(&chip->mixer_lock);
+ 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. */
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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;
+
+ /* FIXME race condition in here!!!
+ chip->mode non-atomic update gets consistently protected
+ by reg_lock always, _except_ for this place!!
+ Probably need to take reg_lock as outer (or inner??) lock, too.
+ (or serialize both lock operations? probably not, though... - racy?)
+ */
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->mixer_lock);
+ 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;
+
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ chip->mode |= SB_RATE_LOCK_PLAYBACK;
+ snd_sbdsp_command(chip, 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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ spin_lock(&chip->reg_lock);
+ result = snd_als4k_gcr_read(chip, ALS4K_GCRA4_FIFO2_CURRENT_ADDR);
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ spin_lock(&chip->reg_lock);
+ result = snd_als4k_gcr_read(chip, ALS4K_GCRA0_FIFO1_CURRENT_ADDR);
+ spin_unlock(&chip->reg_lock);
+ 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);
+
+ spin_lock(&chip->mixer_lock);
+ /* SPECS_PAGE: 20 */
+ sb_irqstatus = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
+ spin_unlock(&chip->mixer_lock);
+
+ 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 */
+ spin_lock_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);
+ spin_unlock_irq(&chip->mixer_lock);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+#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 = request_region(io_port, 8, "ALS4000 gameport");
+ if (r)
+ break;
+ }
+ } else {
+ io_port = joystick_port[dev];
+ r = request_region(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");
+ release_and_free_resource(r);
+ 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;
+ gameport_set_port_data(gp, r);
+
+ /* 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) {
+ struct resource *r = gameport_get_port_data(acard->gameport);
+
+ gameport_unregister_port(acard->gameport);
+ acard->gameport = NULL;
+
+ /* disable joystick */
+ snd_als4000_set_addr(acard->iobase, 0, 0, 0, 0);
+
+ release_and_free_resource(r);
+ }
+}
+#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);
+ pci_release_regions(acard->pci);
+ pci_disable_device(acard->pci);
+}
+
+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 */
+ if ((err = pci_enable_device(pci)) < 0) {
+ return err;
+ }
+ /* check, if we can restrict PCI DMA transfers to 24 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(24)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(24)) < 0) {
+ dev_err(&pci->dev, "architecture does not support 24bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ if ((err = pci_request_regions(pci, "ALS4000")) < 0) {
+ pci_disable_device(pci);
+ 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(*acard) /* private_data: acard */,
+ &card);
+ if (err < 0) {
+ pci_release_regions(pci);
+ pci_disable_device(pci);
+ 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);
+
+ if ((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)) < 0) {
+ goto out_err;
+ }
+ acard->chip = chip;
+
+ chip->pci = pci;
+ chip->alt_port = iobase;
+
+ snd_als4000_configure(chip);
+
+ strcpy(card->driver, "ALS4000");
+ strcpy(card->shortname, "Avance Logic ALS4000");
+ sprintf(card->longname, "%s at 0x%lx, irq %i",
+ card->shortname, chip->alt_port, chip->irq);
+
+ if ((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)) < 0) {
+ dev_err(&pci->dev, "no MPU-401 device at 0x%lx?\n",
+ iobase + ALS4K_IOB_30_MIDI_DATA);
+ goto out_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 */
+
+ if ((err = snd_als4000_pcm(chip, 0)) < 0) {
+ goto out_err;
+ }
+ if ((err = snd_sbmixer_new(chip)) < 0) {
+ goto out_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 {
+ if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+ goto out_err;
+ }
+ }
+
+ snd_als4000_create_gameport(acard, dev);
+
+ if ((err = snd_card_register(card)) < 0) {
+ goto out_err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ err = 0;
+ goto out;
+
+out_err:
+ snd_card_free(card);
+
+out:
+ return err;
+}
+
+static void snd_card_als4000_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(snd_als4000_pm, snd_als4000_suspend, snd_als4000_resume);
+#define SND_ALS4000_PM_OPS &snd_als4000_pm
+#else
+#define SND_ALS4000_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver als4000_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_als4000_ids,
+ .probe = snd_card_als4000_probe,
+ .remove = snd_card_als4000_remove,
+ .driver = {
+ .pm = SND_ALS4000_PM_OPS,
+ },
+};
+
+module_pci_driver(als4000_driver);
diff --git a/sound/pci/asihpi/Makefile b/sound/pci/asihpi/Makefile
new file mode 100644
index 000000000..8351f8f5b
--- /dev/null
+++ b/sound/pci/asihpi/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-asihpi-objs := 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..5e1f9f100
--- /dev/null
+++ b/sound/pci/asihpi/asihpi.c
@@ -0,0 +1,2980 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Asihpi soundcard
+ * Copyright (c) by AudioScience Inc <support@audioscience.com>
+ *
+ * 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 <linux/pci.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/hwdep.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("AudioScience inc. <support@audioscience.com>");
+MODULE_DESCRIPTION("AudioScience ALSA ASI5xxx ASI6xxx ASI87xx ASI89xx "
+ HPI_VER_STRING);
+
+#if defined CONFIG_SND_DEBUG_VERBOSE
+/**
+ * snd_printddd - very verbose debug printk
+ * @format: format string
+ *
+ * Works like snd_printk() for debugging purposes.
+ * Ignored when CONFIG_SND_DEBUG_VERBOSE is not set.
+ * Must set snd module debug parameter to 3 to enable at runtime.
+ */
+#define snd_printddd(format, args...) \
+ __snd_printk(3, __FILE__, __LINE__, format, ##args)
+#else
+#define snd_printddd(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)
+ printk(KERN_WARNING
+ "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)
+{
+ char name[16];
+ snd_pcm_debug_name(substream, name, sizeof(name));
+ snd_printdd("%s HWPARAMS\n", name);
+ snd_printdd(" samplerate=%dHz channels=%d format=%d subformat=%d\n",
+ params_rate(p), params_channels(p),
+ params_format(p), params_subformat(p));
+ snd_printdd(" 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));
+ snd_printdd(" 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 (__force snd_pcm_format_t)(-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(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;
+ }
+ }
+
+ snd_printd(KERN_WARNING "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(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) {
+ snd_printdd(
+ "stream_host_buffer_attach success %u %lu\n",
+ params_buffer_bytes(params),
+ (unsigned long)runtime->dma_addr);
+ } else {
+ snd_printd("stream_host_buffer_attach error %d\n",
+ err);
+ return -ENOMEM;
+ }
+
+ err = 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;
+ del_timer(&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:
+ snd_printdd("%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;
+ snd_printddd("%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) {
+ snd_printdd("%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:
+ snd_printdd("%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*/
+ s->runtime->status->state = SNDRV_PCM_STATE_SETUP;
+
+ if (card->support_grouping) {
+ snd_printdd("%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:
+ snd_printdd("%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:
+ snd_printdd("%s trigger pause push\n", name);
+ card->pcm_stop(substream);
+ hpi_handle_error(hpi_stream_stop(dpcm->h_stream));
+ break;
+ default:
+ snd_printd(KERN_ERR "\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 = from_timer(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;
+ int loops = 0;
+ 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));
+ snd_printdd("P%d start\n", s->number);
+ ds->drained_count = 0;
+ }
+ } else if (state == HPI_STATE_DRAINED) {
+ snd_printd(KERN_WARNING "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);
+ }
+
+ snd_printddd(
+ "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)
+ );
+ loops++;
+ }
+ 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;
+ snd_printddd("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) {
+ snd_printddd("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;
+
+ snd_printddd("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 {
+ snd_printddd("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;
+ snd_printddd("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;
+
+ snd_printdd("P%d prepare\n", substream->number);
+
+ 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);
+ snd_printddd("%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(sizeof(*dpcm), GFP_KERNEL);
+ 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;
+ if (err)
+ 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);
+
+ snd_printdd("playback open\n");
+
+ 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));
+ snd_printdd("playback close\n");
+
+ 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));
+
+ snd_printddd("%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;
+
+ snd_printdd("Capture Prepare %d\n", substream->number);
+ 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(sizeof(*dpcm), GFP_KERNEL);
+ if (dpcm == NULL)
+ return -ENOMEM;
+
+ snd_printdd("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;
+ if (err)
+ 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;
+ strcpy(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)
+{
+ 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)
+ sprintf(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) {
+ sprintf(hpi_ctl->name, "%s %d %s%s",
+ asihpi_dst_names[hpi_ctl->dst_node_type],
+ hpi_ctl->dst_node_index,
+ dir, name);
+ } else {
+ sprintf(hpi_ctl->name, "%s %d %s%s",
+ asihpi_src_names[hpi_ctl->src_node_type],
+ hpi_ctl->src_node_index,
+ dir, name);
+ }
+ /* printk(KERN_INFO "Adding %s %d to %d ", hpi_ctl->name,
+ hpi_ctl->wSrcNodeType, hpi_ctl->wDstNodeType); */
+}
+
+/*------------------------------------------------------------
+ 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;
+ }
+ }
+ snd_printd(KERN_WARNING
+ "Control %x failed to match mux source %hu %hu\n",
+ 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 =
+ (struct snd_card_asihpi *)(kcontrol->private_data);
+ 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;
+
+ strcpy(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 =
+ (struct snd_card_asihpi *)(kcontrol->private_data);
+ 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 =
+ (struct snd_card_asihpi *)(kcontrol->private_data);
+ 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;
+ strcpy(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;
+ strcpy(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);
+
+ strcpy(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[] = {
+ {HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205,
+ HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+ (kernel_ulong_t)HPI_6205},
+ {HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040,
+ HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+ (kernel_ulong_t)HPI_6000},
+ {0,}
+};
+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..3aebec763
--- /dev/null
+++ b/sound/pci/asihpi/hpi.h
@@ -0,0 +1,1724 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/******************************************************************************
+
+ AudioScience HPI driver
+ Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
+
+
+*/
+/** \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 <linux/types.h>
+#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:
+
+<table border=1 cellspacing=0 cellpadding=5>
+<tr>
+<td><p><b>Bitrate (kbs)</b></p>
+<td><p><b>Mono</b></p>
+<td><p><b>Stereo,<br>Joint Stereo or<br>Dual Channel</b></p>
+
+<tr><td>32<td>X<td>_
+<tr><td>40<td>_<td>_
+<tr><td>48<td>X<td>_
+<tr><td>56<td>X<td>_
+<tr><td>64<td>X<td>X
+<tr><td>80<td>X<td>_
+<tr><td>96<td>X<td>X
+<tr><td>112<td>X<td>X
+<tr><td>128<td>X<td>X
+<tr><td>160<td>X<td>X
+<tr><td>192<td>X<td>X
+<tr><td>224<td>_<td>X
+<tr><td>256<td>-<td>X
+<tr><td>320<td>-<td>X
+<tr><td>384<td>_<td>X
+</table>
+*/
+ 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:
+
+<table border=1 cellspacing=0 cellpadding=5>
+<tr>
+<td><p><b>Bitrate (kbs)</b></p>
+<td><p><b>Mono<br>Stereo @ 8,<br>11.025 and<br>12kHz*</b></p>
+<td><p><b>Mono<br>Stereo @ 16,<br>22.050 and<br>24kHz*</b></p>
+<td><p><b>Mono<br>Stereo @ 32,<br>44.1 and<br>48kHz</b></p>
+
+<tr><td>16<td>X<td>X<td>_
+<tr><td>24<td>X<td>X<td>_
+<tr><td>32<td>X<td>X<td>X
+<tr><td>40<td>X<td>X<td>X
+<tr><td>48<td>X<td>X<td>X
+<tr><td>56<td>X<td>X<td>X
+<tr><td>64<td>X<td>X<td>X
+<tr><td>80<td>_<td>X<td>X
+<tr><td>96<td>_<td>X<td>X
+<tr><td>112<td>_<td>X<td>X
+<tr><td>128<td>_<td>X<td>X
+<tr><td>144<td>_<td>X<td>_
+<tr><td>160<td>_<td>X<td>X
+<tr><td>192<td>_<td>_<td>X
+<tr><td>224<td>_<td>_<td>X
+<tr><td>256<td>-<td>_<td>X
+<tr><td>320<td>-<td>_<td>X
+</table>
+\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 /**<highest control type ID */
+/* WARNING types 256 or greater impact bit packing in all AX6 DSP code */
+};
+
+/*******************************************/
+/** Adapter properties
+These are used in HPI_AdapterSetProperty() and HPI_AdapterGetProperty()
+\ingroup adapter
+*/
+enum HPI_ADAPTER_PROPERTIES {
+/** \internal Used in dwProperty field of HPI_AdapterSetProperty() and
+HPI_AdapterGetProperty(). This errata applies to all ASI6000 cards with both
+analog and digital outputs. The CS4224 A/D+D/A has a one sample delay between
+left and right channels on both its input (ADC) and output (DAC).
+More details are available in Cirrus Logic errata ER284B2.
+PDF available from www.cirrus.com, released by Cirrus in 2001.
+*/
+ HPI_ADAPTER_PROPERTY_ERRATA_1 = 1,
+
+/** Adapter grouping property
+Indicates whether the adapter supports the grouping API (for ASIO and SSX2)
+*/
+ HPI_ADAPTER_PROPERTY_GROUPING = 2,
+
+/** Driver SSX2 property
+Tells the kernel driver to turn on SSX2 stream mapping.
+This feature is not used by the DSP. In fact the call is completely processed
+by the driver and is not passed on to the DSP at all.
+*/
+ HPI_ADAPTER_PROPERTY_ENABLE_SSX2 = 3,
+
+/** Adapter SSX2 property
+Indicates the state of the adapter's SSX2 setting. This setting is stored in
+non-volatile memory on the adapter. A typical call sequence would be to use
+HPI_ADAPTER_PROPERTY_SSX2_SETTING to set SSX2 on the adapter and then to reload
+the driver. The driver would query HPI_ADAPTER_PROPERTY_SSX2_SETTING during
+startup and if SSX2 is set, it would then call HPI_ADAPTER_PROPERTY_ENABLE_SSX2
+to enable SSX2 stream mapping within the kernel level of the driver.
+*/
+ HPI_ADAPTER_PROPERTY_SSX2_SETTING = 4,
+
+/** Enables/disables PCI(e) IRQ.
+A setting of 0 indicates that no interrupts are being generated. A DSP boot
+this property is set to 0. Setting to a non-zero value specifies the number
+of frames of audio that should be processed between interrupts. This property
+should be set to multiple of the mixer interval as read back from the
+HPI_ADAPTER_PROPERTY_INTERVAL property.
+*/
+ HPI_ADAPTER_PROPERTY_IRQ_RATE = 5,
+
+/** Base number for readonly properties */
+ HPI_ADAPTER_PROPERTY_READONLYBASE = 256,
+
+/** Readonly adapter latency property.
+This property returns in the input and output latency in samples.
+Property 1 is the estimated input latency
+in samples, while Property 2 is that output latency in samples.
+*/
+ HPI_ADAPTER_PROPERTY_LATENCY = 256,
+
+/** Readonly adapter granularity property.
+The granulariy is the smallest size chunk of stereo samples that is processed by
+the adapter.
+This property returns the record granularity in samples in Property 1.
+Property 2 returns the play granularity.
+*/
+ HPI_ADAPTER_PROPERTY_GRANULARITY = 257,
+
+/** Readonly adapter number of current channels property.
+Property 1 is the number of record channels per record device.
+Property 2 is the number of play channels per playback device.*/
+ HPI_ADAPTER_PROPERTY_CURCHANNELS = 258,
+
+/** Readonly adapter software version.
+The SOFTWARE_VERSION property returns the version of the software running
+on the adapter as Major.Minor.Release.
+Property 1 contains Major in bits 15..8 and Minor in bits 7..0.
+Property 2 contains Release in bits 7..0. */
+ HPI_ADAPTER_PROPERTY_SOFTWARE_VERSION = 259,
+
+/** Readonly adapter MAC address MSBs.
+The MAC_ADDRESS_MSB property returns
+the most significant 32 bits of the MAC address.
+Property 1 contains bits 47..32 of the MAC address.
+Property 2 contains bits 31..16 of the MAC address. */
+ HPI_ADAPTER_PROPERTY_MAC_ADDRESS_MSB = 260,
+
+/** Readonly adapter MAC address LSBs
+The MAC_ADDRESS_LSB property returns
+the least significant 16 bits of the MAC address.
+Property 1 contains bits 15..0 of the MAC address. */
+ HPI_ADAPTER_PROPERTY_MAC_ADDRESS_LSB = 261,
+
+/** Readonly extended adapter type number
+The EXTENDED_ADAPTER_TYPE property returns the 4 digits of an extended
+adapter type, i.e ASI8920-0022, 0022 is the extended type.
+The digits are returned as ASCII characters rather than the hex digits that
+are returned for the main type
+Property 1 returns the 1st two (left most) digits, i.e "00"
+in the example above, the upper byte being the left most digit.
+Property 2 returns the 2nd two digits, i.e "22" in the example above*/
+ HPI_ADAPTER_PROPERTY_EXTENDED_ADAPTER_TYPE = 262,
+
+/** Readonly debug log buffer information */
+ HPI_ADAPTER_PROPERTY_LOGTABLEN = 263,
+ HPI_ADAPTER_PROPERTY_LOGTABBEG = 264,
+
+/** Readonly adapter IP address
+For 192.168.1.101
+Property 1 returns the 1st two (left most) digits, i.e 192*256 + 168
+in the example above, the upper byte being the left most digit.
+Property 2 returns the 2nd two digits, i.e 1*256 + 101 in the example above, */
+ HPI_ADAPTER_PROPERTY_IP_ADDRESS = 265,
+
+/** Readonly adapter buffer processed count. Returns a buffer processed count
+that is incremented every time all buffers for all streams are updated. This
+is useful for checking completion of all stream operations across the adapter
+when using grouped streams.
+*/
+ HPI_ADAPTER_PROPERTY_BUFFER_UPDATE_COUNT = 266,
+
+/** Readonly mixer and stream intervals
+
+These intervals are measured in mixer frames.
+To convert to time, divide by the adapter samplerate.
+
+The mixer interval is the number of frames processed in one mixer iteration.
+The stream update interval is the interval at which streams check for and
+process data, and BBM host buffer counters are updated.
+
+Property 1 is the mixer interval in mixer frames.
+Property 2 is the stream update interval in mixer frames.
+*/
+ HPI_ADAPTER_PROPERTY_INTERVAL = 267,
+/** Adapter capabilities 1
+Property 1 - adapter can do multichannel (SSX1)
+Property 2 - adapter can do stream grouping (supports SSX2)
+*/
+ HPI_ADAPTER_PROPERTY_CAPS1 = 268,
+/** Adapter capabilities 2
+Property 1 - adapter can do samplerate conversion (MRX)
+Property 2 - adapter can do timestretch (TSX)
+*/
+ HPI_ADAPTER_PROPERTY_CAPS2 = 269,
+
+/** Readonly adapter sync header connection count.
+*/
+ HPI_ADAPTER_PROPERTY_SYNC_HEADER_CONNECTIONS = 270,
+/** Readonly supports SSX2 property.
+Indicates the adapter supports SSX2 in some mode setting. The
+return value is true (1) or false (0). If the current adapter
+mode is MONO SSX2 is disabled, even though this property will
+return true.
+*/
+ HPI_ADAPTER_PROPERTY_SUPPORTS_SSX2 = 271,
+/** Readonly supports PCI(e) IRQ.
+Indicates that the adapter in it's current mode supports interrupts
+across the host bus. Note, this does not imply that interrupts are
+enabled. Instead it indicates that they can be enabled.
+*/
+ HPI_ADAPTER_PROPERTY_SUPPORTS_IRQ = 272,
+/** Readonly supports firmware updating.
+Indicates that the adapter implements an interface to update firmware
+on the adapter.
+*/
+ HPI_ADAPTER_PROPERTY_SUPPORTS_FW_UPDATE = 273,
+/** Readonly Firmware IDs
+Identifiy firmware independent of individual adapter type.
+May be used as a filter for firmware update images.
+Property 1 = Bootloader ID
+Property 2 = Main program ID
+*/
+ HPI_ADAPTER_PROPERTY_FIRMWARE_ID = 274
+};
+
+/** Adapter mode commands
+
+Used in wQueryOrSet parameter of HPI_AdapterSetModeEx().
+\ingroup adapter
+*/
+enum HPI_ADAPTER_MODE_CMDS {
+ /** Set the mode to the given parameter */
+ HPI_ADAPTER_MODE_SET = 0,
+ /** Return 0 or error depending whether mode is valid,
+ but don't set the mode */
+ HPI_ADAPTER_MODE_QUERY = 1
+};
+
+/** Adapter Modes
+ These are used by HPI_AdapterSetModeEx()
+
+\warning - more than 16 possible modes breaks
+a bitmask in the Windows WAVE DLL
+\ingroup adapter
+*/
+enum HPI_ADAPTER_MODES {
+/** 4 outstream mode.
+- ASI6114: 1 instream
+- ASI6044: 4 instreams
+- ASI6012: 1 instream
+- ASI6102: no instreams
+- ASI6022, ASI6122: 2 instreams
+- ASI5111, ASI5101: 2 instreams
+- ASI652x, ASI662x: 2 instreams
+- ASI654x, ASI664x: 4 instreams
+*/
+ HPI_ADAPTER_MODE_4OSTREAM = 1,
+
+/** 6 outstream mode.
+- ASI6012: 1 instream,
+- ASI6022, ASI6122: 2 instreams
+- ASI652x, ASI662x: 4 instreams
+*/
+ HPI_ADAPTER_MODE_6OSTREAM = 2,
+
+/** 8 outstream mode.
+- ASI6114: 8 instreams
+- ASI6118: 8 instreams
+- ASI6585: 8 instreams
+*/
+ HPI_ADAPTER_MODE_8OSTREAM = 3,
+
+/** 16 outstream mode.
+- ASI6416 16 instreams
+- ASI6518, ASI6618 16 instreams
+- ASI6118 16 mono out and in streams
+*/
+ HPI_ADAPTER_MODE_16OSTREAM = 4,
+
+/** one outstream mode.
+- ASI5111 1 outstream, 1 instream
+*/
+ HPI_ADAPTER_MODE_1OSTREAM = 5,
+
+/** ASI504X mode 1. 12 outstream, 4 instream 0 to 48kHz sample rates
+ (see ASI504X datasheet for more info).
+*/
+ HPI_ADAPTER_MODE_1 = 6,
+
+/** ASI504X mode 2. 4 outstreams, 4 instreams at 0 to 192kHz sample rates
+ (see ASI504X datasheet for more info).
+*/
+ HPI_ADAPTER_MODE_2 = 7,
+
+/** ASI504X mode 3. 4 outstreams, 4 instreams at 0 to 192kHz sample rates
+ (see ASI504X datasheet for more info).
+*/
+ HPI_ADAPTER_MODE_3 = 8,
+
+/** ASI504X multichannel mode.
+ 2 outstreams -> 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_get_assert2(u16 adapter_index, u16 *p_assert_count,
+ char *psz_assert, u32 *p_param1, u32 *p_param2,
+ u32 *p_dsp_string_addr, u16 *p_processor_id);
+
+u16 hpi_adapter_test_assert(u16 adapter_index, u16 assert_id);
+
+u16 hpi_adapter_enable_capability(u16 adapter_index, u16 capability, u32 key);
+
+u16 hpi_adapter_self_test(u16 adapter_index);
+
+u16 hpi_adapter_debug_read(u16 adapter_index, u32 dsp_address, char *p_bytes,
+ int *count_bytes);
+
+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);
+
+u16 hpi_pad_get_program_type_string(u32 h_control, const u32 data_type,
+ const u32 pTY, char *psz_string, const u32 string_length);
+
+/****************************/
+/* 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..aa4d06353
--- /dev/null
+++ b/sound/pci/asihpi/hpi6000.c
@@ -0,0 +1,1801 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/******************************************************************************
+
+ AudioScience HPI driver
+ Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
+
+
+ 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 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(sizeof(struct hpi_hw_obj), GFP_KERNEL);
+ 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 (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" */
+ strcpy(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;
+ u32 loop_count = 0;
+
+ 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;
+ loop_count++;
+ }
+
+ 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. <support@audioscience.com>
+
+
+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..4cdaeefeb
--- /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. <support@audioscience.com>
+
+
+ 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 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(sizeof(struct hpi_hw_obj), GFP_KERNEL);
+ 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; i<dwLength; i++) */
+ i = 0;
+ {
+ test_addr = start_address + i * 4;
+ test_data = 0x00000001;
+ for (j = 0; j < 32; j++) {
+ boot_loader_write_mem32(pao, dsp_index, test_addr,
+ test_data);
+ data = boot_loader_read_mem32(pao, dsp_index,
+ test_addr);
+ if (data != test_data) {
+ HPI_DEBUG_LOG(VERBOSE,
+ "Memtest error details "
+ "%08x %08x %08x %i\n", test_addr,
+ test_data, data, dsp_index);
+ return 1; /* error */
+ }
+ test_data = test_data << 1;
+ } /* for(j) */
+ } /* for(i) */
+
+ /* for the next 100 locations test each location, leaving it as zero */
+ /* write a zero to the next word in memory before we read */
+ /* the previous write to make sure every memory location is unique */
+ for (i = 0; i < 100; i++) {
+ test_addr = start_address + i * 4;
+ test_data = 0xA5A55A5A;
+ boot_loader_write_mem32(pao, dsp_index, test_addr, test_data);
+ boot_loader_write_mem32(pao, dsp_index, test_addr + 4, 0);
+ data = boot_loader_read_mem32(pao, dsp_index, test_addr);
+ if (data != test_data) {
+ HPI_DEBUG_LOG(VERBOSE,
+ "Memtest error details "
+ "%08x %08x %08x %i\n", test_addr, test_data,
+ data, dsp_index);
+ return 1; /* error */
+ }
+ /* leave location as zero */
+ boot_loader_write_mem32(pao, dsp_index, test_addr, 0x0);
+ }
+
+ /* zero out entire memory block */
+ for (i = 0; i < length; i++) {
+ test_addr = start_address + i * 4;
+ boot_loader_write_mem32(pao, dsp_index, test_addr, 0x0);
+ }
+ return 0;
+}
+
+static u16 boot_loader_test_internal_memory(struct hpi_adapter_obj *pao,
+ int dsp_index)
+{
+ int err = 0;
+ if (dsp_index == 0) {
+ /* DSP 0 is a C6205 */
+ /* 64K prog mem */
+ err = boot_loader_test_memory(pao, dsp_index, 0x00000000,
+ 0x10000);
+ if (!err)
+ /* 64K data mem */
+ err = boot_loader_test_memory(pao, dsp_index,
+ 0x80000000, 0x10000);
+ } else if (dsp_index == 1) {
+ /* DSP 1 is a C6713 */
+ /* 192K internal mem */
+ err = boot_loader_test_memory(pao, dsp_index, 0x00000000,
+ 0x30000);
+ if (!err)
+ /* 64K internal mem / L2 cache */
+ err = boot_loader_test_memory(pao, dsp_index,
+ 0x00030000, 0x10000);
+ }
+
+ if (err)
+ return HPI6205_ERROR_DSP_INTMEM;
+ else
+ return 0;
+}
+
+static u16 boot_loader_test_external_memory(struct hpi_adapter_obj *pao,
+ int dsp_index)
+{
+ u32 dRAM_start_address = 0;
+ u32 dRAM_size = 0;
+
+ if (dsp_index == 0) {
+ /* only test for SDRAM if an ASI5000 card */
+ if (pao->pci.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. <support@audioscience.com>
+
+
+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..6859d5138
--- /dev/null
+++ b/sound/pci/asihpi/hpi_internal.h
@@ -0,0 +1,1424 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/******************************************************************************
+
+ AudioScience HPI driver
+ Copyright (C) 1997-2012 AudioScience Inc. <support@audioscience.com>
+
+
+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_subsys_create_adapter(const struct hpi_resource *p_resource,
+ u16 *pw_adapter_index);
+
+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);
+
+u16 hpi_adapter_restart(u16 adapter_index);
+
+/*
+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..7d1abaedb
--- /dev/null
+++ b/sound/pci/asihpi/hpicmn.c
@@ -0,0 +1,713 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/******************************************************************************
+
+ AudioScience HPI driver
+ Copyright (C) 1997-2014 AudioScience Inc. <support@audioscience.com>
+
+
+\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;
+ }
+
+ *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 =
+ kmalloc(sizeof(*p_cache), GFP_KERNEL);
+ if (!p_cache)
+ return NULL;
+
+ p_cache->p_info =
+ kcalloc(control_count, sizeof(*p_cache->p_info), GFP_KERNEL);
+ if (!p_cache->p_info) {
+ kfree(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)
+{
+ if (p_cache) {
+ kfree(p_cache->p_info);
+ 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..de3bedd29
--- /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. <support@audioscience.com>
+
+
+*/
+
+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 allocated memory of lookup pointers. */
+ struct hpi_control_cache_info **p_info;
+ /** pointer to DSP's control cache. */
+ u8 *p_cache;
+};
+
+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..f37856ab0
--- /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. <support@audioscience.com>
+
+
+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 = (len + cols - 1) / 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..c24ed69eb
--- /dev/null
+++ b/sound/pci/asihpi/hpidebug.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*****************************************************************************
+
+ AudioScience HPI driver
+ Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
+
+
+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
+#define FILE_LINE SOURCEFILE_NAME ":" __stringify(__LINE__) " "
+#else
+#define FILE_LINE __FILE__ ":" __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..9acc0ac75
--- /dev/null
+++ b/sound/pci/asihpi/hpidspcd.c
@@ -0,0 +1,131 @@
+// 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. <support@audioscience.com>
+
+
+***********************************************************************/
+#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;
+ 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 error1;
+ }
+ if (firmware->size < sizeof(header)) {
+ dev_err(&dev->dev, "Header size too small %s\n", fw_name);
+ goto error2;
+ }
+ 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 error2;
+ }
+
+ 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 error2;
+ }
+
+ 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(sizeof(*dsp_code->pvt), GFP_KERNEL);
+ if (!dsp_code->pvt) {
+ err_ret = HPI_ERROR_MEMORY_ALLOC;
+ goto error2;
+ }
+
+ dsp_code->pvt->dev = dev;
+ dsp_code->pvt->firmware = firmware;
+ dsp_code->header = header;
+ dsp_code->block_length = header.size / sizeof(u32);
+ dsp_code->word_count = sizeof(header) / sizeof(u32);
+ return 0;
+
+error2:
+ release_firmware(firmware);
+error1:
+ 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..a01e8c609
--- /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. <support@audioscience.com>
+
+
+\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..1de053831
--- /dev/null
+++ b/sound/pci/asihpi/hpifunc.c
@@ -0,0 +1,2869 @@
+// 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, &param);
+ 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, &param);
+ 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.obj_index = hm.obj_index;
+ 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. <support@audioscience.com>
+
+
+ Hardware Programming Interface (HPI) Utility functions.
+
+ (C) Copyright AudioScience Inc. 2007
+*******************************************************************************/
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+#include <linux/nospec.h>
+
+/* 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. <support@audioscience.com>
+
+
+ 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..f7427f8eb
--- /dev/null
+++ b/sound/pci/asihpi/hpimsgx.c
@@ -0,0 +1,798 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/******************************************************************************
+
+ AudioScience HPI driver
+ Copyright (C) 1997-2014 AudioScience Inc. <support@audioscience.com>
+
+
+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_subsys_response {
+ struct hpi_response_header h;
+ struct hpi_subsys_res s;
+};
+
+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], &hr,
+ sizeof(rESP_HPI_ADAPTER_OPEN[0]));
+ 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) {
+ /* 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. <support@audioscience.com>
+
+
+ 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..477a5b4b5
--- /dev/null
+++ b/sound/pci/asihpi/hpioctl.c
@@ -0,0 +1,594 @@
+// 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. <support@audioscience.com>
+
+
+*******************************************************************************/
+#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 <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/uaccess.h>
+#include <linux/pci.h>
+#include <linux/stringify.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/nospec.h>
+
+#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(sizeof(*hm), GFP_KERNEL);
+ hr = kzalloc(sizeof(*hr), GFP_KERNEL);
+ 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_printk(KERN_DEBUG, &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] =
+ ioremap(pci_resource_start(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:
+ while (--idx >= 0) {
+ if (pci.ap_mem_base[idx]) {
+ iounmap(pci.ap_mem_base[idx]);
+ pci.ap_mem_base[idx] = NULL;
+ }
+ }
+
+ 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)
+{
+ int idx;
+ struct hpi_message hm;
+ struct hpi_response hr;
+ struct hpi_adapter *pa;
+ struct hpi_pci pci;
+
+ pa = pci_get_drvdata(pci_dev);
+ pci = pa->adapter->pci;
+
+ /* 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);
+
+ /* unmap PCI memory space, mapped during device init. */
+ for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx)
+ iounmap(pci.ap_mem_base[idx]);
+
+ 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. <support@audioscience.com>
+
+
+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. <support@audioscience.com>
+
+
+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 <linux/delay.h>
+#include <linux/sched.h>
+
+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. <support@audioscience.com>
+
+
+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 <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+
+#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 <linux/spinlock.h>
+
+#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..0673e8278
--- /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. <support@audioscience.com>
+
+
+ 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.
+*/
+
+{
+HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205,
+ HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+ (kernel_ulong_t) HPI_6205}
+, {
+HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040,
+ HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+ (kernel_ulong_t) HPI_6000}
+, {
+0}
diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c
new file mode 100644
index 000000000..a25d75455
--- /dev/null
+++ b/sound/pci/atiixp.c
@@ -0,0 +1,1677 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ATI IXP 150/200/250/300 AC97 controllers
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ATI IXP AC97 controller");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ATI,IXP150/200/250/300/400/600}}");
+
+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), 0 }, /* SB200 */
+ { PCI_VDEVICE(ATI, 0x4361), 0 }, /* SB300 */
+ { PCI_VDEVICE(ATI, 0x4370), 0 }, /* SB400 */
+ { PCI_VDEVICE(ATI, 0x4382), 0 }, /* SB600 */
+ { 0, }
+};
+
+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;
+ unsigned long flags;
+
+ 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 */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ writel(0, chip->remap_addr + dma->ops->llp_offset);
+ dma->ops->enable_dma(chip, 0);
+ dma->ops->enable_dma(chip, 1);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ /* 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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;
+}
+#endif
+
+/*
+ * 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;
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&chip->reg_lock);
+ 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);
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_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);
+
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ }
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ runtime->private_data = dma;
+
+ /* enable DMA bits */
+ spin_lock_irq(&chip->reg_lock);
+ dma->ops->enable_dma(chip, 1);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ dma->ops->enable_dma(chip, 0);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ mutex_lock(&chip->open_mutex);
+ err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0);
+ mutex_unlock(&chip->open_mutex);
+ 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);
+ int err;
+ mutex_lock(&chip->open_mutex);
+ err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]);
+ mutex_unlock(&chip->open_mutex);
+ return err;
+}
+
+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);
+ int err;
+ mutex_lock(&chip->open_mutex);
+ if (chip->spdif_over_aclink) /* share DMA_PLAYBACK */
+ err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 2);
+ else
+ err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_SPDIF], -1);
+ mutex_unlock(&chip->open_mutex);
+ return err;
+}
+
+static int snd_atiixp_spdif_close(struct snd_pcm_substream *substream)
+{
+ struct atiixp *chip = snd_pcm_substream_chip(substream);
+ int err;
+ mutex_lock(&chip->open_mutex);
+ if (chip->spdif_over_aclink)
+ err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]);
+ else
+ err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_SPDIF]);
+ mutex_unlock(&chip->open_mutex);
+ return err;
+}
+
+/* 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;
+ strcpy(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)
+ strcpy(pcm->name, "ATI IXP IEC958 (AC97)");
+ else
+ strcpy(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;
+ spin_lock(&chip->reg_lock);
+ chip->codec_not_ready_bits |= detected;
+ atiixp_update(chip, IER, detected, 0); /* disable the detected irqs */
+ spin_unlock(&chip->reg_lock);
+ }
+
+ /* 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;
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 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;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i])) < 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;
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(snd_atiixp_pm, snd_atiixp_suspend, snd_atiixp_resume);
+#define SND_ATIIXP_PM_OPS &snd_atiixp_pm
+#else
+#define SND_ATIIXP_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+
+/*
+ * 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 int snd_atiixp_free(struct atiixp *chip)
+{
+ if (chip->irq < 0)
+ goto __hw_end;
+ snd_atiixp_chip_stop(chip);
+
+ __hw_end:
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ iounmap(chip->remap_addr);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_atiixp_dev_free(struct snd_device *device)
+{
+ struct atiixp *chip = device->device_data;
+ return snd_atiixp_free(chip);
+}
+
+/*
+ * constructor for chip instance
+ */
+static int snd_atiixp_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct atiixp **r_chip)
+{
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_atiixp_dev_free,
+ };
+ struct atiixp *chip;
+ int err;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&chip->reg_lock);
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+ if ((err = pci_request_regions(pci, "ATI IXP AC97")) < 0) {
+ pci_disable_device(pci);
+ kfree(chip);
+ return err;
+ }
+ chip->addr = pci_resource_start(pci, 0);
+ chip->remap_addr = pci_ioremap_bar(pci, 0);
+ if (chip->remap_addr == NULL) {
+ dev_err(card->dev, "AC'97 space ioremap problem\n");
+ snd_atiixp_free(chip);
+ return -EIO;
+ }
+
+ if (request_irq(pci->irq, snd_atiixp_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_atiixp_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ pci_set_master(pci);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_atiixp_free(chip);
+ return err;
+ }
+
+ *r_chip = chip;
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, spdif_aclink ? "ATIIXP" : "ATIIXP-SPDMA");
+ strcpy(card->shortname, "ATI IXP");
+ if ((err = snd_atiixp_create(card, pci, &chip)) < 0)
+ goto __error;
+ card->private_data = chip;
+
+ if ((err = snd_atiixp_aclink_reset(chip)) < 0)
+ goto __error;
+
+ chip->spdif_over_aclink = spdif_aclink;
+
+ if ((err = snd_atiixp_mixer_new(chip, ac97_clock, ac97_quirk)) < 0)
+ goto __error;
+
+ if ((err = snd_atiixp_pcm_new(chip)) < 0)
+ goto __error;
+
+ 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);
+
+ if ((err = snd_card_register(card)) < 0)
+ goto __error;
+
+ pci_set_drvdata(pci, card);
+ return 0;
+
+ __error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_atiixp_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver atiixp_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_atiixp_ids,
+ .probe = snd_atiixp_probe,
+ .remove = snd_atiixp_remove,
+ .driver = {
+ .pm = SND_ATIIXP_PM_OPS,
+ },
+};
+
+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..ae88217d6
--- /dev/null
+++ b/sound/pci/atiixp_modem.c
@@ -0,0 +1,1302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ATI IXP 150/200/250 AC97 modem controllers
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ATI IXP MC97 controller");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ATI,IXP150/200/250}}");
+
+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), 0 }, /* SB200 */
+ { PCI_VDEVICE(ATI, 0x4378), 0 }, /* SB400 */
+ { 0, }
+};
+
+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;
+ unsigned long flags;
+
+ 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 */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ writel(0, chip->remap_addr + dma->ops->llp_offset);
+ dma->ops->enable_dma(chip, 0);
+ dma->ops->enable_dma(chip, 1);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ /* 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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;
+}
+#endif
+
+/*
+ * 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;
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_rates)) < 0)
+ return err;
+ if ((err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ runtime->private_data = dma;
+
+ /* enable DMA bits */
+ spin_lock_irq(&chip->reg_lock);
+ dma->ops->enable_dma(chip, 1);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ dma->ops->enable_dma(chip, 0);
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+ int err;
+
+ mutex_lock(&chip->open_mutex);
+ err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0);
+ mutex_unlock(&chip->open_mutex);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_atiixp_playback_close(struct snd_pcm_substream *substream)
+{
+ struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+ int err;
+ mutex_lock(&chip->open_mutex);
+ err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]);
+ mutex_unlock(&chip->open_mutex);
+ return err;
+}
+
+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;
+ strcpy(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;
+ spin_lock(&chip->reg_lock);
+ chip->codec_not_ready_bits |= detected;
+ atiixp_update(chip, IER, detected, 0); /* disable the detected irqs */
+ spin_unlock(&chip->reg_lock);
+ }
+
+ /* 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;
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 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;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i])) < 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;
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(snd_atiixp_pm, snd_atiixp_suspend, snd_atiixp_resume);
+#define SND_ATIIXP_PM_OPS &snd_atiixp_pm
+#else
+#define SND_ATIIXP_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+/*
+ * 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 int snd_atiixp_free(struct atiixp_modem *chip)
+{
+ if (chip->irq < 0)
+ goto __hw_end;
+ snd_atiixp_chip_stop(chip);
+
+ __hw_end:
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ iounmap(chip->remap_addr);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_atiixp_dev_free(struct snd_device *device)
+{
+ struct atiixp_modem *chip = device->device_data;
+ return snd_atiixp_free(chip);
+}
+
+/*
+ * constructor for chip instance
+ */
+static int snd_atiixp_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct atiixp_modem **r_chip)
+{
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_atiixp_dev_free,
+ };
+ struct atiixp_modem *chip;
+ int err;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&chip->reg_lock);
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+ if ((err = pci_request_regions(pci, "ATI IXP MC97")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->addr = pci_resource_start(pci, 0);
+ chip->remap_addr = pci_ioremap_bar(pci, 0);
+ if (chip->remap_addr == NULL) {
+ dev_err(card->dev, "AC'97 space ioremap problem\n");
+ snd_atiixp_free(chip);
+ return -EIO;
+ }
+
+ if (request_irq(pci->irq, snd_atiixp_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_atiixp_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ pci_set_master(pci);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_atiixp_free(chip);
+ return err;
+ }
+
+ *r_chip = chip;
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, "ATIIXP-MODEM");
+ strcpy(card->shortname, "ATI IXP Modem");
+ if ((err = snd_atiixp_create(card, pci, &chip)) < 0)
+ goto __error;
+ card->private_data = chip;
+
+ if ((err = snd_atiixp_aclink_reset(chip)) < 0)
+ goto __error;
+
+ if ((err = snd_atiixp_mixer_new(chip, ac97_clock)) < 0)
+ goto __error;
+
+ if ((err = snd_atiixp_pcm_new(chip)) < 0)
+ goto __error;
+
+ 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);
+
+ if ((err = snd_card_register(card)) < 0)
+ goto __error;
+
+ pci_set_drvdata(pci, card);
+ return 0;
+
+ __error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_atiixp_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver atiixp_modem_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_atiixp_ids,
+ .probe = snd_atiixp_probe,
+ .remove = snd_atiixp_remove,
+ .driver = {
+ .pm = SND_ATIIXP_PM_OPS,
+ },
+};
+
+module_pci_driver(atiixp_modem_driver);
diff --git a/sound/pci/au88x0/Makefile b/sound/pci/au88x0/Makefile
new file mode 100644
index 000000000..78ab11562
--- /dev/null
+++ b/sound/pci/au88x0/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-au8810-objs := au8810.o
+snd-au8820-objs := au8820.o
+snd-au8830-objs := 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..b2bfa50bf
--- /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), 1,},
+ {0,}
+};
+
+#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<<FIFO_SIZE_BITS) // 0x20
+#define FIFO_MASK (FIFO_SIZE-1) //0x1f /* at shift left 0xc */
+//#define FIFO_MASK 0x1f /* at shift left 0xb */
+//#define FIFO_SIZE 0x20
+#define FIFO_BITS 0x03880000
+#define VORTEX_FIFO_ADBDATA 0x14000
+#define VORTEX_FIFO_WTDATA 0x10000
+
+/* CODEC */
+#define VORTEX_CODEC_CTRL 0x29184
+#define VORTEX_CODEC_EN 0x29190
+#define EN_CODEC0 0x00000300
+#define EN_AC98 0x00000c00 /* Modem AC98 slots. */
+#define EN_CODEC1 0x00003000
+#define EN_CODEC (EN_CODEC0 | EN_CODEC1)
+#define EN_SPORT 0x00030000
+#define EN_SPDIF 0x000c0000
+
+#define VORTEX_CODEC_CHN 0x29080
+#define VORTEX_CODEC_IO 0x29188
+
+/* SPDIF */
+#define VORTEX_SPDIF_FLAGS 0x2205c
+#define VORTEX_SPDIF_CFG0 0x291D0
+#define VORTEX_SPDIF_CFG1 0x291D4
+#define VORTEX_SPDIF_SMPRATE 0x29194
+
+/* Sample timer */
+#define VORTEX_SMP_TIME 0x29198
+
+#define VORTEX_MODEM_CTRL 0x291ac
+
+/* IRQ */
+#define VORTEX_IRQ_SOURCE 0x2a000 /* Interrupt source flags. */
+#define VORTEX_IRQ_CTRL 0x2a004 /* Interrupt source mask. */
+
+#define VORTEX_STAT 0x2a008 /* Status */
+
+#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 0x01004000
+#define CTRL_IRQ_ENABLE 0x00004000
+
+/* write: Timer period config / read: TIMER IRQ ack. */
+#define VORTEX_IRQ_STAT 0x2919c
+
+/* DMA */
+#define VORTEX_ENGINE_CTRL 0x27ae8
+#define ENGINE_INIT 0x1380000
+
+/* MIDI *//* GAME. */
+#define VORTEX_MIDI_DATA 0x28800
+#define VORTEX_MIDI_CMD 0x28804 /* Write command / Read status */
+
+#define VORTEX_CTRL2 0x2880c
+#define CTRL2_GAME_ADCMODE 0x40
+#define VORTEX_GAME_LEGACY 0x28808
+#define VORTEX_GAME_AXIS 0x28810
+#define AXIS_SIZE 4
+#define AXIS_RANGE 0x1fff
diff --git a/sound/pci/au88x0/au8820.c b/sound/pci/au88x0/au8820.c
new file mode 100644
index 000000000..dbc2263b4
--- /dev/null
+++ b/sound/pci/au88x0/au8820.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "au8820.h"
+#include "au88x0.h"
+static const struct pci_device_id snd_vortex_ids[] = {
+ {PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_VORTEX_1), 0,},
+ {0,}
+};
+
+#include "au88x0_synth.c"
+#include "au88x0_core.c"
+#include "au88x0_pcm.c"
+#include "au88x0_mpu401.c"
+#include "au88x0_game.c"
+#include "au88x0_mixer.c"
+#include "au88x0.c"
diff --git a/sound/pci/au88x0/au8820.h b/sound/pci/au88x0/au8820.h
new file mode 100644
index 000000000..8a128e8fe
--- /dev/null
+++ b/sound/pci/au88x0/au8820.h
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ Aureal Vortex Soundcard driver.
+
+ IO addr collected from asp4core.vxd:
+ function address
+ 0005D5A0 13004
+ 00080674 14004
+ 00080AFF 12818
+
+ */
+
+#define CHIP_AU8820
+
+#define CARD_NAME "Aureal Vortex"
+#define CARD_NAME_SHORT "au8820"
+
+/* Number of ADB and WT channels */
+#define NR_ADB 0x10
+#define NR_WT 0x20
+#define NR_SRC 0x10
+#define NR_A3D 0x00
+#define NR_MIXIN 0x10
+#define NR_MIXOUT 0x10
+
+
+/* ADBDMA */
+#define VORTEX_ADBDMA_STAT 0x105c0 /* 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 0x10580 /* 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 masks and shift also work for the wtdma, if not specified otherwise.
+#define VORTEX_ADBDMA_BUFCFG0 0x10400
+#define VORTEX_ADBDMA_BUFCFG1 0x10404
+#define VORTEX_ADBDMA_BUFBASE 0x10200
+#define VORTEX_ADBDMA_START 0x106c0 /* Which subbuffer starts */
+#define VORTEX_ADBDMA_STATUS 0x10600 /* stored at AdbDma->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<<FIFO_SIZE_BITS) // 0x20
+#define FIFO_MASK (FIFO_SIZE-1) //0x1f /* at shift left 0xc */
+#define VORTEX_FIFO_ADBDATA 0xe000
+#define VORTEX_FIFO_WTDATA 0xe800
+
+/* CODEC */
+#define VORTEX_CODEC_CTRL 0x11984
+#define VORTEX_CODEC_EN 0x11990
+#define EN_CODEC 0x00000300
+#define EN_SPORT 0x00030000
+#define EN_SPDIF 0x000c0000
+#define VORTEX_CODEC_CHN 0x11880
+#define VORTEX_CODEC_IO 0x11988
+
+#define VORTEX_SPDIF_FLAGS 0x1005c /* FIXME */
+#define VORTEX_SPDIF_CFG0 0x119D0
+#define VORTEX_SPDIF_CFG1 0x119D4
+#define VORTEX_SPDIF_SMPRATE 0x11994
+
+/* Sample timer */
+#define VORTEX_SMP_TIME 0x11998
+
+/* IRQ */
+#define VORTEX_IRQ_SOURCE 0x12800 /* Interrupt source flags. */
+#define VORTEX_IRQ_CTRL 0x12804 /* Interrupt source mask. */
+
+#define VORTEX_STAT 0x12808 /* ?? */
+
+#define VORTEX_CTRL 0x1280c
+#define CTRL_MIDI_EN 0x00000001
+#define CTRL_MIDI_PORT 0x00000060
+#define CTRL_GAME_EN 0x00000008
+#define CTRL_GAME_PORT 0x00000e00
+#define CTRL_IRQ_ENABLE 0x4000
+
+/* write: Timer period config / read: TIMER IRQ ack. */
+#define VORTEX_IRQ_STAT 0x1199c
+
+/* DMA */
+#define VORTEX_DMA_BUFFER 0x10200
+#define VORTEX_ENGINE_CTRL 0x1060c
+#define ENGINE_INIT 0x0L
+
+ /* MIDI *//* GAME. */
+#define VORTEX_MIDI_DATA 0x11000
+#define VORTEX_MIDI_CMD 0x11004 /* Write command / Read status */
+#define VORTEX_GAME_LEGACY 0x11008
+#define VORTEX_CTRL2 0x1100c
+#define CTRL2_GAME_ADCMODE 0x40
+#define VORTEX_GAME_AXIS 0x11010
+#define AXIS_SIZE 4
+#define AXIS_RANGE 0x1fff
diff --git a/sound/pci/au88x0/au8830.c b/sound/pci/au88x0/au8830.c
new file mode 100644
index 000000000..e963c4e2f
--- /dev/null
+++ b/sound/pci/au88x0/au8830.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "au8830.h"
+#include "au88x0.h"
+static const struct pci_device_id snd_vortex_ids[] = {
+ {PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_VORTEX_2), 0,},
+ {0,}
+};
+
+#include "au88x0_synth.c"
+#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/au8830.h b/sound/pci/au88x0/au8830.h
new file mode 100644
index 000000000..40f671ffd
--- /dev/null
+++ b/sound/pci/au88x0/au8830.h
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ Aureal Vortex Soundcard driver.
+
+ IO addr collected from asp4core.vxd:
+ function address
+ 0005D5A0 13004
+ 00080674 14004
+ 00080AFF 12818
+
+ */
+
+#define CHIP_AU8830
+
+#define CARD_NAME "Aureal Vortex 2"
+#define CARD_NAME_SHORT "au8830"
+
+#define NR_ADB 0x20
+#define NR_SRC 0x10
+#define NR_A3D 0x10
+#define NR_MIXIN 0x20
+#define NR_MIXOUT 0x10
+#define NR_WT 0x40
+
+/* 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 0x27a00 /* 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
+#define ADB_FIFO_EN_SHIFT 0x15
+#define ADB_FIFO_EN (1 << 0x15)
+// The ADB masks and shift also are valid for the wtdma, except if specified otherwise.
+#define VORTEX_ADBDMA_BUFCFG0 0x27800
+#define VORTEX_ADBDMA_BUFCFG1 0x27804
+#define VORTEX_ADBDMA_BUFBASE 0x27400
+#define VORTEX_ADBDMA_START 0x27c00 /* Which subbuffer starts */
+
+#define VORTEX_ADBDMA_STATUS 0x27A90 /* stored at AdbDma->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) ((x<x20)?(x + OFFSET_WT0):(x + OFFSET_WT1))
+#define ADB_WTOUT(x,y) (((x)==0)?((y) + OFFSET_WT0):((y) + OFFSET_WT1))
+#define ADB_XTALKIN(x) ((x) + OFFSET_XTALKIN)
+#define ADB_XTALKOUT(x) ((x) + OFFSET_XTALKOUT)
+
+#define MIX_DEFIGAIN 0x08
+#define MIX_DEFOGAIN 0x08 /* 0x8->6dB (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..be276fb3f
--- /dev/null
+++ b/sound/pci/au88x0/au88x0.c
@@ -0,0 +1,384 @@
+// 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 comunicate.
+ *
+ * Now at least i can play Legacy DOOM with MIDI music :-)
+ */
+
+#include "au88x0.h"
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <sound/initval.h>
+
+// 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_SUPPORTED_DEVICE("{{Aureal Semiconductor Inc., Aureal Vortex Sound Processor}}");
+
+MODULE_DEVICE_TABLE(pci, snd_vortex_ids);
+
+static void vortex_fix_latency(struct pci_dev *vortex)
+{
+ int rc;
+ if (!(rc = pci_write_config_byte(vortex, 0x40, 0xff))) {
+ 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
+ */
+
+ if (!(rc = pci_read_config_byte(via, 0x42, &value))
+ && ((value & 0x10)
+ || !(rc = pci_write_config_byte(via, 0x42, value | 0x10)))) {
+ 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)))
+ vortex_fix_agp_bridge(via);
+ if ((fix & 0x4) && (via = pci_get_device(PCI_VENDOR_ID_VIA,
+ PCI_DEVICE_ID_VIA_82C598_1, NULL)))
+ vortex_fix_agp_bridge(via);
+ if ((fix & 0x8) && (via = pci_get_device(PCI_VENDOR_ID_AMD,
+ PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL)))
+ vortex_fix_agp_bridge(via);
+ }
+ pci_dev_put(via);
+}
+
+// component-destructor
+// (see "Management of Cards and Components")
+static int snd_vortex_dev_free(struct snd_device *device)
+{
+ vortex_t *vortex = device->device_data;
+
+ vortex_gameport_unregister(vortex);
+ vortex_core_shutdown(vortex);
+ // Take down PCI interface.
+ free_irq(vortex->irq, vortex);
+ iounmap(vortex->mmio);
+ pci_release_regions(vortex->pci_dev);
+ pci_disable_device(vortex->pci_dev);
+ kfree(vortex);
+
+ return 0;
+}
+
+// chip-specific constructor
+// (see "Management of Cards and Components")
+static int
+snd_vortex_create(struct snd_card *card, struct pci_dev *pci, vortex_t ** rchip)
+{
+ vortex_t *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_vortex_dev_free,
+ };
+
+ *rchip = NULL;
+
+ // check PCI availability (DMA).
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) {
+ dev_err(card->dev, "error to set DMA mask\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+
+ // initialize the stuff
+ chip->pci_dev = pci;
+ chip->io = pci_resource_start(pci, 0);
+ chip->vendor = pci->vendor;
+ chip->device = pci->device;
+ chip->card = card;
+ chip->irq = -1;
+
+ // (1) PCI resource allocation
+ // Get MMIO area
+ //
+ if ((err = pci_request_regions(pci, CARD_NAME_SHORT)) != 0)
+ goto regions_out;
+
+ chip->mmio = pci_ioremap_bar(pci, 0);
+ if (!chip->mmio) {
+ dev_err(card->dev, "MMIO area remap failed.\n");
+ err = -ENOMEM;
+ goto ioremap_out;
+ }
+
+ /* 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 */
+ if ((err = vortex_core_init(chip)) != 0) {
+ dev_err(card->dev, "hw core init failed\n");
+ goto core_out;
+ }
+
+ if ((err = request_irq(pci->irq, vortex_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME,
+ chip)) != 0) {
+ dev_err(card->dev, "cannot grab irq\n");
+ goto irq_out;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ pci_set_master(pci);
+ // End of PCI setup.
+
+ // Register alsa root device.
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ goto alloc_out;
+ }
+
+ *rchip = chip;
+
+ return 0;
+
+ alloc_out:
+ free_irq(chip->irq, chip);
+ irq_out:
+ vortex_core_shutdown(chip);
+ core_out:
+ iounmap(chip->mmio);
+ ioremap_out:
+ pci_release_regions(chip->pci_dev);
+ regions_out:
+ pci_disable_device(chip->pci_dev);
+ //FIXME: this not the right place to unregister the gameport
+ vortex_gameport_unregister(chip);
+ kfree(chip);
+ return err;
+}
+
+// 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ // (3)
+ if ((err = snd_vortex_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ snd_vortex_workaround(pci, pcifix[dev]);
+
+ // Card details needed in snd_vortex_midi
+ strcpy(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) {
+ snd_card_free(card);
+ return err;
+ }
+ // ADB pcm.
+ err = snd_vortex_new_pcm(chip, VORTEX_PCM_ADB, NR_PCM);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#ifndef CHIP_AU8820
+ // ADB SPDIF
+ if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_SPDIF, 1)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ // A3D
+ if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_A3D, NR_A3D)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#endif
+ /*
+ // ADB I2S
+ if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_I2S, 1)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ */
+#ifndef CHIP_AU8810
+ // WT pcm.
+ if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_WT, NR_WT)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#endif
+ if ((err = snd_vortex_midi(chip)) < 0) {
+ snd_card_free(card);
+ 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);
+ strcpy(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)
+ if ((err = pci_read_config_word(pci, PCI_DEVICE_ID,
+ &(chip->device))) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = pci_read_config_word(pci, PCI_VENDOR_ID,
+ &(chip->vendor))) < 0) {
+ snd_card_free(card);
+ return 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");
+ snd_card_free(card);
+ err = -ENODEV;
+ return err;
+ }
+#endif
+
+ // (6)
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ // (7)
+ pci_set_drvdata(pci, card);
+ dev++;
+ vortex_connect_default(chip, 1);
+ vortex_enable_int(chip);
+ return 0;
+}
+
+// destructor -- see "Destructor" sub-section
+static void snd_vortex_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+// pci_driver definition
+static struct pci_driver vortex_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_vortex_ids,
+ .probe = snd_vortex_probe,
+ .remove = snd_vortex_remove,
+};
+
+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 <linux/pci.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/hwdep.h>
+#include <sound/ac97_codec.h>
+#include <sound/tlv.h>
+
+#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..2db183f88
--- /dev/null
+++ b/sound/pci/au88x0/au88x0_a3d.c
@@ -0,0 +1,902 @@
+// 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 = kcontrol->private_data;
+ /* 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 = kcontrol->private_data;
+ 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 = kcontrol->private_data;
+ 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 = kcontrol->private_data;
+ 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 = kcontrol->private_data;
+ 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++) {
+ if ((kcontrol =
+ snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+ return -ENOMEM;
+ kcontrol->id.numid = CTRLID_HRTF;
+ kcontrol->info = snd_vortex_a3d_hrtf_info;
+ kcontrol->put = snd_vortex_a3d_hrtf_put;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+ return err;
+ }
+ /* ITD controls. */
+ for (i = 0; i < NR_A3D; i++) {
+ if ((kcontrol =
+ snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+ return -ENOMEM;
+ kcontrol->id.numid = CTRLID_ITD;
+ kcontrol->info = snd_vortex_a3d_itd_info;
+ kcontrol->put = snd_vortex_a3d_itd_put;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+ return err;
+ }
+ /* ILD (gains) controls. */
+ for (i = 0; i < NR_A3D; i++) {
+ if ((kcontrol =
+ snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+ return -ENOMEM;
+ kcontrol->id.numid = CTRLID_GAINS;
+ kcontrol->info = snd_vortex_a3d_ild_info;
+ kcontrol->put = snd_vortex_a3d_ild_put;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+ return err;
+ }
+ /* Filter controls. */
+ for (i = 0; i < NR_A3D; i++) {
+ if ((kcontrol =
+ snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+ return -ENOMEM;
+ kcontrol->id.numid = CTRLID_FILTER;
+ kcontrol->info = snd_vortex_a3d_filter_info;
+ kcontrol->put = snd_vortex_a3d_filter_put;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 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 <openal.h>
+
+#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..0b04436ac
--- /dev/null
+++ b/sound/pci/au88x0/au88x0_core.c
@@ -0,0 +1,2861 @@
+// 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 <linux/delay.h>
+
+/* 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 thru 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 thru 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 thru 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 absolut 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 absolut 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;
+ if ((dma =
+ vortex_adb_checkinout(vortex, NULL, en,
+ VORTEX_RESOURCE_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++) {
+ if ((src[i] = vortex_adb_checkinout(vortex,
+ stream->resources, en,
+ VORTEX_RESOURCE_SRC)) < 0) {
+ memset(stream->resources, 0,
+ sizeof(stream->resources));
+ return -EBUSY;
+ }
+ if (stream->type != VORTEX_PCM_A3D) {
+ if ((mix[i] = vortex_adb_checkinout(vortex,
+ stream->resources,
+ en,
+ VORTEX_RESOURCE_MIXIN)) < 0) {
+ memset(stream->resources,
+ 0,
+ sizeof(stream->resources));
+ return -EBUSY;
+ }
+ }
+ }
+ }
+#ifndef CHIP_AU8820
+ if (stream->type == VORTEX_PCM_A3D) {
+ if ((a3d =
+ vortex_adb_checkinout(vortex,
+ stream->resources, en,
+ VORTEX_RESOURCE_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++) {
+ if ((mix[i] =
+ vortex_adb_checkinout(vortex,
+ stream->resources, en,
+ VORTEX_RESOURCE_MIXOUT))
+ < 0) {
+ memset(stream->resources, 0,
+ sizeof(stream->resources));
+ return -EBUSY;
+ }
+ if ((src[i] =
+ vortex_adb_checkinout(vortex,
+ stream->resources, en,
+ VORTEX_RESOURCE_SRC)) < 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 <period> 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..58e92f2a7
--- /dev/null
+++ b/sound/pci/au88x0/au88x0_eq.c
@@ -0,0 +1,918 @@
+// 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);
+
+ if ((kcontrol =
+ snd_ctl_new1(&vortex_eqtoggle_kcontrol, vortex)) == NULL)
+ return -ENOMEM;
+ kcontrol->private_value = 0;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+ return err;
+
+ /* EQ gain controls */
+ for (i = 0; i < 10; i++) {
+ if ((kcontrol =
+ snd_ctl_new1(&vortex_eq_kcontrol, vortex)) == NULL)
+ return -ENOMEM;
+ snprintf(kcontrol->id.name, sizeof(kcontrol->id.name),
+ "%s Playback Volume", EqBandLabels[i]);
+ kcontrol->private_value = i;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+ return err;
+ //vortex->eqctrl[i] = kcontrol;
+ }
+ /* EQ band levels */
+ if ((kcontrol = snd_ctl_new1(&vortex_levels_kcontrol, vortex)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(vortex->card, kcontrol)) < 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 <vojtech@suse.cz>, 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 <linux/time.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include "au88x0.h"
+#include <linux/gameport.h>
+#include <linux/export.h>
+
+#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..5b647682b
--- /dev/null
+++ b/sound/pci/au88x0/au88x0_mixer.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Vortex Mixer support.
+ *
+ * There is much more than just the AC97 mixer...
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#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));
+ strcpy(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,
+ };
+
+ if ((err = snd_ac97_bus(vortex->card, 0, &ops, NULL, &pbus)) < 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..603494e7d
--- /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 <perex@perex.cz>
+ * Routines for control of MPU-401 in UART mode
+ *
+ * Modified for the Aureal Vortex based Soundcards
+ * by Manuel Jander (mjande@embedded.cl).
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#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
+ if ((temp =
+ snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_MPU401, 0x330,
+ MPU401_INFO_IRQ_HOOK, -1, &rmidi)) != 0) {
+ 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);
+ if ((temp =
+ snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_AUREAL, port,
+ MPU401_INFO_INTEGRATED | MPU401_INFO_MMIO |
+ MPU401_INFO_IRQ_HOOK, -1, &rmidi)) != 0) {
+ 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..d019aa566
--- /dev/null
+++ b/sound/pci/au88x0/au88x0_pcm.c
@@ -0,0 +1,681 @@
+// 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 <sound/asoundef.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#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 */
+ if ((err =
+ snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ /* Avoid PAGE_SIZE boundary to fall inside of a period. */
+ if ((err =
+ snd_pcm_hw_constraint_pow2(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES)) < 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;
+ if ((err = snd_ctl_add(chip->card, kctl)) < 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..f9045afb4
--- /dev/null
+++ b/sound/pci/aw2/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-aw2-objs := 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..f1865afed
--- /dev/null
+++ b/sound/pci/aw2/aw2-alsa.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ *****************************************************************************/
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+#include "saa7146.h"
+#include "aw2-saa7146.h"
+
+MODULE_AUTHOR("Cedric Bregardis <cedric.bregardis@free.fr>, "
+ "Jean-Christian Hassler <jhassler@free.fr>");
+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_dev_free(struct snd_device *device);
+static int snd_aw2_create(struct snd_card *card,
+ struct pci_dev *pci, struct aw2 **rchip);
+static int snd_aw2_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id);
+static void snd_aw2_remove(struct pci_dev *pci);
+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_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0, 0,
+ 0, 0, 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,
+ .remove = snd_aw2_remove,
+};
+
+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 int snd_aw2_dev_free(struct snd_device *device)
+{
+ struct aw2 *chip = device->device_data;
+
+ /* Free hardware */
+ snd_aw2_saa7146_free(&chip->saa7146);
+
+ /* release the irq */
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *)chip);
+ /* release the i/o ports & memory */
+ iounmap(chip->iobase_virt);
+ pci_release_regions(chip->pci);
+ /* disable the PCI entry */
+ pci_disable_device(chip->pci);
+ /* release the data */
+ kfree(chip);
+
+ return 0;
+}
+
+/* chip-specific constructor */
+static int snd_aw2_create(struct snd_card *card,
+ struct pci_dev *pci, struct aw2 **rchip)
+{
+ struct aw2 *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_aw2_dev_free,
+ };
+
+ *rchip = NULL;
+
+ /* initialize the PCI entry */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+ pci_set_master(pci);
+
+ /* check PCI availability (32bit DMA) */
+ if ((dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) ||
+ (dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0)) {
+ dev_err(card->dev, "Impossible to set 32bit mask DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ /* initialize the stuff */
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ /* (1) PCI resource allocation */
+ err = pci_request_regions(pci, "Audiowerk2");
+ if (err < 0) {
+ pci_disable_device(pci);
+ kfree(chip);
+ return err;
+ }
+ chip->iobase_phys = pci_resource_start(pci, 0);
+ chip->iobase_virt =
+ ioremap(chip->iobase_phys,
+ pci_resource_len(pci, 0));
+
+ if (chip->iobase_virt == NULL) {
+ dev_err(card->dev, "unable to remap memory region");
+ pci_release_regions(pci);
+ pci_disable_device(pci);
+ kfree(chip);
+ return -ENOMEM;
+ }
+
+ /* (2) initialization of the chip hardware */
+ snd_aw2_saa7146_setup(&chip->saa7146, chip->iobase_virt);
+
+ if (request_irq(pci->irq, snd_aw2_saa7146_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "Cannot grab irq %d\n", pci->irq);
+
+ iounmap(chip->iobase_virt);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ free_irq(chip->irq, (void *)chip);
+ iounmap(chip->iobase_virt);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return err;
+ }
+
+ *rchip = chip;
+
+ 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ /* (3) Create main component */
+ err = snd_aw2_create(card, pci, &chip);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* initialize mutex */
+ mutex_init(&chip->mtx);
+ /* init spinlock */
+ spin_lock_init(&chip->reg_lock);
+ /* (4) Define driver ID and name string */
+ strcpy(card->driver, "aw2");
+ strcpy(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) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* (7) Set PCI driver data */
+ pci_set_drvdata(pci, card);
+
+ dev++;
+ return 0;
+}
+
+/* destructor */
+static void snd_aw2_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+/* 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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&chip->mtx);
+
+ 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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&chip->mtx);
+
+ return 0;
+}
+
+/* playback trigger callback */
+static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int status = 0;
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ spin_lock(&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:
+ status = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ return status;
+}
+
+/* capture trigger callback */
+static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int status = 0;
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ spin_lock(&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:
+ status = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ return status;
+}
+
+/* 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 */
+ strcpy(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 */
+ strcpy(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 */
+ strcpy(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 <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ *****************************************************************************/
+
+#define AW2_SAA7146_M
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#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..b5c5a71c0
--- /dev/null
+++ b/sound/pci/aw2/aw2-saa7146.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * 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
+
+typedef void (*snd_aw2_saa7146_it_cb) (void *);
+
+struct snd_aw2_saa7146_cb_param {
+ snd_aw2_saa7146_it_cb p_it_callback;
+ void *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 <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ * 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 <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * 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..77c7030eb
--- /dev/null
+++ b/sound/pci/azt3328.c
@@ -0,0 +1,2719 @@
+// 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 <andi AT lisas.de>
+ *
+ * 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 <linux/io.h>
+#include <linux/init.h>
+#include <linux/bug.h> /* WARN_ONCE */
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+/*
+ * 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 <sound/ac97_codec.h>
+#endif
+#include "azt3328.h"
+
+MODULE_AUTHOR("Andreas Mohr <andi AT lisas.de>");
+MODULE_DESCRIPTION("Aztech AZF3328 (PCI168)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}");
+
+#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;
+
+#ifdef CONFIG_PM_SLEEP
+ /* 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];
+#endif
+};
+
+static const struct pci_device_id snd_azf3328_ids[] = {
+ { 0x122D, 0x50DC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* PCI168/3328 */
+ { 0x122D, 0x80DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* 3328 */
+ { 0, }
+};
+
+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(const struct snd_azf3328_codec_data *codec,
+ unsigned reg,
+ u32 value
+)
+{
+ outl(value, 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
+snd_azf3328_game_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
+{
+ outb(value, chip->game_io + reg);
+}
+
+static inline void
+snd_azf3328_game_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
+{
+ outw(value, chip->game_io + reg);
+}
+
+static inline u8
+snd_azf3328_game_inb(const struct snd_azf3328 *chip, unsigned reg)
+{
+ return inb(chip->game_io + reg);
+}
+
+static inline u16
+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(&reg, 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(&reg, 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(&reg, 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(&reg, 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(&reg, 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(&reg, 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) {
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip))) < 0)
+ return err;
+ }
+ snd_component_add(card, "AZF3328 mixer");
+ strcpy(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
+)
+{
+ unsigned long flags;
+ 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:
+ snd_printk(KERN_WARNING "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;
+
+ spin_lock_irqsave(codec->lock, flags);
+
+ /* 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
+ );
+
+ spin_unlock_irqrestore(codec->lock, flags);
+}
+
+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 */
+
+ unsigned long flags;
+
+ /* width 32bit (prevent overflow): */
+ u32 area_length;
+ struct codec_setup_io {
+ u32 dma_start_1;
+ u32 dma_start_2;
+ u32 dma_lengths;
+ } __attribute__((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);
+
+ spin_lock_irqsave(codec->lock, flags);
+ snd_azf3328_codec_outl_multi(
+ codec, IDX_IO_CODEC_DMA_START_1, &setup_io, 3
+ );
+ spin_unlock_irqrestore(codec->lock, flags);
+ }
+}
+
+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);
+
+ spin_lock(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);
+ spin_unlock(codec->lock);
+
+ snd_azf3328_codec_setdmaa(chip, codec, runtime->dma_addr,
+ snd_pcm_lib_period_bytes(substream),
+ snd_pcm_lib_buffer_bytes(substream)
+ );
+
+ spin_lock(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
+ spin_unlock(codec->lock);
+ 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 */
+ spin_lock(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
+ );
+ spin_unlock(codec->lock);
+ 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
+ );
+ }
+
+ spin_lock(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);
+ spin_unlock(codec->lock);
+ 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;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!chip))
+ return 0;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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;
+
+ spin_lock(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);
+ spin_unlock(codec->lock);
+
+ 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 */
+ spin_lock(&chip->reg_lock);
+ snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07);
+ spin_unlock(&chip->reg_lock);
+ 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;
+ strcpy(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;
+ strcpy(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 long flags;
+ 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;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_azf3328_ctrl_outl(chip, IDX_IO_TIMER_VALUE, delay);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int
+snd_azf3328_timer_stop(struct snd_timer *timer)
+{
+ struct snd_azf3328 *chip;
+ unsigned long flags;
+
+ chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ /* 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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;
+
+ strcpy(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 int
+snd_azf3328_free(struct snd_azf3328 *chip)
+{
+ if (chip->irq < 0)
+ goto __end_hw;
+
+ snd_azf3328_mixer_reset(chip);
+
+ snd_azf3328_timer_stop(chip->timer);
+ snd_azf3328_gameport_free(chip);
+
+__end_hw:
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+
+ kfree(chip);
+ return 0;
+}
+
+static int
+snd_azf3328_dev_free(struct snd_device *device)
+{
+ struct snd_azf3328 *chip = device->device_data;
+ return snd_azf3328_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 **rchip)
+{
+ struct snd_azf3328 *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_azf3328_dev_free,
+ };
+ u8 dma_init;
+ enum snd_azf3328_codec_type codec_type;
+ struct snd_azf3328_codec_data *codec_setup;
+
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ err = -ENOMEM;
+ goto out_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(&pci->dev, DMA_BIT_MASK(24)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(24)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 24bit PCI busmaster DMA\n"
+ );
+ err = -ENXIO;
+ goto out_err;
+ }
+
+ err = pci_request_regions(pci, "Aztech AZF3328");
+ if (err < 0)
+ goto out_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 (request_irq(pci->irq, snd_azf3328_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ err = -EBUSY;
+ goto out_err;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ pci_set_master(pci);
+
+ snd_azf3328_debug_show_ports(chip);
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0)
+ goto out_err;
+
+ /* create mixer interface & switches */
+ err = snd_azf3328_mixer_new(chip);
+ if (err < 0)
+ goto out_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 = 1;
+ snd_azf3328_ctrl_codec_activity(chip, codec_type, 0);
+
+ spin_lock_irq(codec->lock);
+ snd_azf3328_codec_outb(codec, IDX_IO_CODEC_DMA_FLAGS,
+ dma_init);
+ spin_unlock_irq(codec->lock);
+ }
+
+ *rchip = chip;
+
+ err = 0;
+ goto out;
+
+out_err:
+ if (chip)
+ snd_azf3328_free(chip);
+ pci_disable_device(pci);
+
+out:
+ return err;
+}
+
+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) {
+ err = -ENODEV;
+ goto out;
+ }
+ if (!enable[dev]) {
+ dev++;
+ err = -ENOENT;
+ goto out;
+ }
+
+ err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ goto out;
+
+ strcpy(card->driver, "AZF3328");
+ strcpy(card->shortname, "Aztech AZF3328 (PCI168)");
+
+ err = snd_azf3328_create(card, pci, pci_id->driver_data, &chip);
+ if (err < 0)
+ goto out_err;
+
+ card->private_data = chip;
+
+ /* 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
+ );
+ goto out_err;
+ }
+
+ err = snd_azf3328_timer(chip, 0);
+ if (err < 0)
+ goto out_err;
+
+ err = snd_azf3328_pcm(chip);
+ if (err < 0)
+ goto out_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)
+ goto out_err;
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ goto out_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)
+ goto out_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++;
+
+ err = 0;
+ goto out;
+
+out_err:
+ dev_err(card->dev, "something failed, exiting\n");
+ snd_card_free(card);
+
+out:
+ return err;
+}
+
+static void
+snd_azf3328_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(snd_azf3328_pm, snd_azf3328_suspend, snd_azf3328_resume);
+#define SND_AZF3328_PM_OPS &snd_azf3328_pm
+#else
+#define SND_AZF3328_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver azf3328_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_azf3328_ids,
+ .probe = snd_azf3328_probe,
+ .remove = snd_azf3328_remove,
+ .driver = {
+ .pm = SND_AZF3328_PM_OPS,
+ },
+};
+
+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..54cb223ca
--- /dev/null
+++ b/sound/pci/bt87x.c
@@ -0,0 +1,967 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * bt87x.c - Brooktree Bt878/Bt879 driver for ALSA
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * based on btaudio.c by Gerd Knorr <kraxel@bytesex.org>
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Brooktree Bt87x audio driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Brooktree,Bt878},"
+ "{Brooktree,Bt879}}");
+
+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 = (irq_block * chip->lines + 15) / 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);
+
+ spin_lock_irq(&chip->reg_lock);
+ chip->reg_control |= CTL_A_PWRDN;
+ snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_bt87x_start(struct snd_bt87x *chip)
+{
+ spin_lock(&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);
+ spin_unlock(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_bt87x_stop(struct snd_bt87x *chip)
+{
+ spin_lock(&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);
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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 int snd_bt87x_free(struct snd_bt87x *chip)
+{
+ if (chip->mmio)
+ snd_bt87x_stop(chip);
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ iounmap(chip->mmio);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_bt87x_dev_free(struct snd_device *device)
+{
+ struct snd_bt87x *chip = device->device_data;
+ return snd_bt87x_free(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;
+ strcpy(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 **rchip)
+{
+ struct snd_bt87x *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_bt87x_dev_free
+ };
+
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+ spin_lock_init(&chip->reg_lock);
+
+ if ((err = pci_request_regions(pci, "Bt87x audio")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->mmio = pci_ioremap_bar(pci, 0);
+ if (!chip->mmio) {
+ dev_err(card->dev, "cannot remap io memory\n");
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ 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 = request_irq(pci->irq, snd_bt87x_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip);
+ if (err < 0) {
+ dev_err(card->dev, "cannot grab irq %d\n", pci->irq);
+ goto fail;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ pci_set_master(pci);
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0)
+ goto fail;
+
+ *rchip = chip;
+ return 0;
+
+fail:
+ snd_bt87x_free(chip);
+ return err;
+}
+
+#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 "
+ "<alsa-devel@alsa-project.org>\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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ err = snd_bt87x_create(card, pci, &chip);
+ if (err < 0)
+ goto _error;
+
+ 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)
+ goto _error;
+ }
+ if (!chip->board.no_analog) {
+ err = snd_bt87x_pcm(chip, DEVICE_ANALOG, "Bt87x Analog");
+ if (err < 0)
+ goto _error;
+ err = snd_ctl_add(card, snd_ctl_new1(
+ &snd_bt87x_capture_volume, chip));
+ if (err < 0)
+ goto _error;
+ err = snd_ctl_add(card, snd_ctl_new1(
+ &snd_bt87x_capture_boost, chip));
+ if (err < 0)
+ goto _error;
+ err = snd_ctl_add(card, snd_ctl_new1(
+ &snd_bt87x_capture_source, chip));
+ if (err < 0)
+ goto _error;
+ }
+ 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);
+
+ strcpy(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);
+ strcpy(card->mixername, "Bt87x");
+
+ 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 snd_bt87x_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+/* 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,
+ .remove = snd_bt87x_remove,
+};
+
+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..9e51d3df3
--- /dev/null
+++ b/sound/pci/ca0106/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-ca0106-objs := 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..62a22ca3b
--- /dev/null
+++ b/sound/pci/ca0106/ca0106.h
@@ -0,0 +1,727 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ * 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 <fmoraes@nc.rr.com>
+ */
+
+/************************************************************************************************/
+/* PCI function 0 registers, address = <val> + PCIBASE0 */
+/************************************************************************************************/
+
+#define 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 DATA 0x04 /* Indexed register set data register */
+ /* DATA[31:0] */
+
+#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_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 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 UNKNOWN10 0x10 /* Unknown ??. Defaults to 0 */
+#define 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 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 AC97DATA 0x1c /* AC97 register set data register (16 bit) */
+
+#define 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<<channel_id) */
+ /* Capture (0x100<<channel_id) */
+ /* Playback sample rate 96000 = 0x20000 */
+ /* Start Playback [3:0] (one bit per channel)
+ * Start Capture [11:8] (one bit per channel)
+ * Playback rate [23:16] (2 bits per channel) (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
+ * Playback mixer in enable [27:24] (one bit per channel)
+ * Playback mixer out enable [31:28] (one bit per channel)
+ */
+/* The Digital out jack is shared with the Center/LFE Analogue output.
+ * The jack has 4 poles. I will call 1 - Tip, 2 - Next to 1, 3 - Next to 2, 4 - Next to 3
+ * For Analogue: 1 -> 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<front><rear><center-lfe><side>
+ -> 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;
+ struct resource *res_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..c189f70c8
--- /dev/null
+++ b/sound/pci/ca0106/ca0106_main.c
@@ -0,0 +1,1899 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ * 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 <fmoraes@nc.rr.com>
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("James Courtier-Dutton <James@superbug.demon.co.uk>");
+MODULE_DESCRIPTION("CA0106");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative,SB CA0106 chip}}");
+
+// 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 long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+void snd_ca0106_ptr_write(struct snd_ca0106 *emu,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+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 long flags;
+ unsigned int intr_enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ intr_enable = inl(emu->port + INTE) | intrenb;
+ outl(intr_enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_ca0106_intr_disable(struct snd_ca0106 *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int intr_enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ intr_enable = inl(emu->port + INTE) & ~intrenb;
+ outl(intr_enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+
+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(sizeof(*epcm), GFP_KERNEL);
+
+ 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;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 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(sizeof(*epcm), GFP_KERNEL);
+ 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;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ //snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_capture_period_sizes);
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 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 + HCFG) ;
+ hcfg = (hcfg & ~hcfg_mask) | hcfg_set;
+ outl(hcfg, emu->port + 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 + HCFG) ;
+ hcfg = (hcfg & ~hcfg_mask) | hcfg_set;
+ outl(hcfg, emu->port + 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<<channel));
+ snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0)|(0x100<<channel));
+ epcm->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<<channel));
+ snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel));
+ epcm->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;
+ unsigned long flags;
+ unsigned short val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ val = inw(emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_ca0106_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg, unsigned short val)
+{
+ struct snd_ca0106 *emu = ac97->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ outw(val, emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+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,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 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 int snd_ca0106_free(struct snd_ca0106 *chip)
+{
+ if (chip->res_port != NULL) {
+ /* avoid access to already used hardware */
+ ca0106_stop_chip(chip);
+ }
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ // release the data
+#if 1
+ if (chip->buffer.area)
+ snd_dma_free_pages(&chip->buffer);
+#endif
+
+ // release the i/o port
+ release_and_free_resource(chip->res_port);
+
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_ca0106_dev_free(struct snd_device *device)
+{
+ struct snd_ca0106 *chip = device->device_data;
+ return snd_ca0106_free(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 + 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+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;
+ strcpy(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 + 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 + AC97ADDRESS);
+ outw(0x8000, chip->port + 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+GPIO);
+ /* outl(0x00f0e000, chip->port+GPIO); */ /* Analog */
+ outl(0x005f5301, chip->port+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+GPIO);
+ /* outl(0x00f0e000, chip->port+GPIO); */ /* Analog */
+ outl(0x005f5301, chip->port+GPIO); /* Analog */
+ } else {
+ outl(0x0, chip->port+GPIO);
+ outl(0x005f03a3, chip->port+GPIO); /* Analog */
+ /* outl(0x005f02a2, chip->port+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+HCFG); */
+ /* outl(0x00000009, chip->port+HCFG); */
+ /* AC97 2.0, Enable outputs. */
+ outl(HCFG_AC97 | HCFG_AUDIOENABLE, chip->port+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 + 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 + 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 **rchip)
+{
+ struct snd_ca0106 *chip;
+ const struct snd_ca0106_details *c;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_ca0106_dev_free,
+ };
+
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) {
+ dev_err(card->dev, "error to set 32bit mask DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ spin_lock_init(&chip->emu_lock);
+
+ chip->port = pci_resource_start(pci, 0);
+ chip->res_port = request_region(chip->port, 0x20, "snd_ca0106");
+ if (!chip->res_port) {
+ snd_ca0106_free(chip);
+ dev_err(card->dev, "cannot allocate the port\n");
+ return -EBUSY;
+ }
+
+ if (request_irq(pci->irq, snd_ca0106_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip)) {
+ snd_ca0106_free(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. */
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ 1024, &chip->buffer) < 0) {
+ snd_ca0106_free(chip);
+ 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);
+ strcpy(card->driver, "CA0106");
+ strcpy(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);
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ snd_ca0106_free(chip);
+ return err;
+ }
+ *rchip = chip;
+ 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;
+
+ if ((err = ca_midi_init(chip, midi, 0, name)) < 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ err = snd_ca0106_create(dev, card, pci, &chip);
+ if (err < 0)
+ goto error;
+ card->private_data = chip;
+
+ for (i = 0; i < 4; i++) {
+ err = snd_ca0106_pcm(chip, i);
+ if (err < 0)
+ goto error;
+ }
+
+ if (chip->details->ac97 == 1) {
+ /* The SB0410 and SB0413 do not have an AC97 chip. */
+ err = snd_ca0106_ac97(chip);
+ if (err < 0)
+ goto error;
+ }
+ err = snd_ca0106_mixer(chip);
+ if (err < 0)
+ goto error;
+
+ dev_dbg(card->dev, "probe for MIDI channel A ...");
+ err = snd_ca0106_midi(chip, CA0106_MIDI_CHAN_A);
+ if (err < 0)
+ goto error;
+ 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)
+ goto error;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+ error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_ca0106_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+#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), 0 }, /* Audigy LS or Live 24bit */
+ { 0, }
+};
+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,
+ .remove = snd_ca0106_remove,
+ .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..c852c6a75
--- /dev/null
+++ b/sound/pci/ca0106/ca0106_mixer.c
@@ -0,0 +1,918 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ * 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 <fmoraes@nc.rr.com>
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <linux/io.h>
+
+#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 + GPIO) & ~0x101;
+ outl(val, emu->port + 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 + GPIO) | 0x101;
+ outl(val, emu->port + 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+GPIO) & ~0x400;
+ tmp = tmp | 0x400;
+ outl(tmp, emu->port+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+GPIO) & ~0x400;
+ outl(tmp, emu->port+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));
+ strcpy(id.name, name);
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_remove_id(card, &id);
+}
+
+static struct snd_kcontrol *ctl_find(struct snd_card *card, const char *name)
+{
+ struct snd_ctl_elem_id sid;
+ memset(&sid, 0, sizeof(sid));
+ /* FIXME: strcpy is bad. */
+ strcpy(sid.name, name);
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_find_id(card, &sid);
+}
+
+static int rename_ctl(struct snd_card *card, const char *src, const char *dst)
+{
+ struct snd_kcontrol *kctl = ctl_find(card, src);
+ if (kctl) {
+ strcpy(kctl->id.name, 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
+};
+
+static void add_followers(struct snd_card *card,
+ struct snd_kcontrol *master, const char * const *list)
+{
+ for (; *list; list++) {
+ struct snd_kcontrol *follower = ctl_find(card, *list);
+ if (follower)
+ snd_ctl_add_follower(master, follower);
+ }
+}
+
+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;
+ add_followers(card, vmaster, follower_vols);
+
+ 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;
+ add_followers(card, vmaster, follower_sws);
+ }
+
+ strcpy(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..c99603e13
--- /dev/null
+++ b/sound/pci/ca0106/ca0106_proc.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ * 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 <fmoraes@nc.rr.com>
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+
+#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;
+ unsigned long flags;
+ char line[64];
+ u32 reg, val;
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if (reg < 0x40 && val <= 0xffffffff) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(val, emu->port + (reg & 0xfffffffc));
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ }
+ }
+}
+
+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;
+ unsigned long flags;
+ int i;
+ snd_iprintf(buffer, "Registers:\n\n");
+ for(i = 0; i < 0x20; i+=4) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inl(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ 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;
+ unsigned long flags;
+ int i;
+ snd_iprintf(buffer, "Registers:\n\n");
+ for(i = 0; i < 0x20; i+=2) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inw(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ 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;
+ unsigned long flags;
+ int i;
+ snd_iprintf(buffer, "Registers:\n\n");
+ for(i = 0; i < 0x20; i+=1) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inb(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ 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", &reg, &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", &reg, &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..18524e0a9
--- /dev/null
+++ b/sound/pci/ca0106/ca_midi.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
+ * 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 <perex@perex.cz>
+ * emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ */
+
+#include <linux/spinlock.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#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;
+ }
+
+ spin_lock(&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);
+
+
+ }
+ }
+ spin_unlock(&midi->input_lock);
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&midi->output_lock);
+
+}
+
+static void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack)
+{
+ unsigned long flags;
+ int timeout, ok;
+
+ spin_lock_irqsave(&midi->input_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&midi->input_lock, flags);
+ 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;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!midi->dev_id))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= CA_MIDI_MODE_INPUT;
+ midi->substream_input = substream;
+ if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ ca_midi_cmd(midi, midi->reset, 1);
+ ca_midi_cmd(midi, midi->enter_uart, 1);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int ca_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ca_midi *midi = substream->rmidi->private_data;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!midi->dev_id))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= CA_MIDI_MODE_OUTPUT;
+ midi->substream_output = substream;
+ if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ ca_midi_cmd(midi, midi->reset, 1);
+ ca_midi_cmd(midi, midi->enter_uart, 1);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int ca_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ca_midi *midi = substream->rmidi->private_data;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!midi->dev_id))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ 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)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ ca_midi_cmd(midi, midi->reset, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int ca_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ca_midi *midi = substream->rmidi->private_data;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!midi->dev_id))
+ return -ENXIO;
+
+ spin_lock_irqsave(&midi->open_lock, flags);
+
+ 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)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ ca_midi_cmd(midi, midi->reset, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ 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;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!midi->dev_id))
+ return;
+
+ if (up) {
+ int max = 4;
+ unsigned char byte;
+
+ spin_lock_irqsave(&midi->output_lock, flags);
+
+ /* 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 */
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ return;
+ }
+ ca_midi_write_data(midi, byte);
+ max--;
+ } else {
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ 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;
+
+ if ((err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi)) < 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);
+
+ strcpy(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 <tilde@tk-sls.de>
+ * Creative Audio MIDI, for the CA0106 Driver
+ * Version: 0.0.1
+ *
+ * Changelog:
+ * See ca_midi.c
+ */
+
+#include <linux/spinlock.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+
+#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..120dd8b33
--- /dev/null
+++ b/sound/pci/cmipci.c
@@ -0,0 +1,3367 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for C-Media CMI8338 and 8738 PCI soundcards.
+ * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
+ */
+
+/* Does not work. Warning may block system in capture mode */
+/* #define USE_VAR48KRATE */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("C-Media CMI8x38 PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8738},"
+ "{C-Media,CMI8738B},"
+ "{C-Media,CMI8338A},"
+ "{C-Media,CMI8338B}}");
+
+#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;
+
+#ifdef CONFIG_PM_SLEEP
+ unsigned int saved_regs[0x20];
+ unsigned char saved_mixers[0x20];
+#endif
+};
+
+
+/* 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 <werner@suse.de>.
+ */
+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) {
+ mutex_lock(&cm->open_mutex);
+ if (cm->opened[CM_CH_PLAY]) {
+ mutex_unlock(&cm->open_mutex);
+ return -EBUSY;
+ }
+ /* reserve the channel A */
+ cm->opened[CM_CH_PLAY] = CM_OPEN_PLAYBACK_MULTI;
+ mutex_unlock(&cm->open_mutex);
+ }
+ 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) {
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+ }
+ 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;
+ }
+
+ spin_lock_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;
+ spin_unlock_irq(&cm->reg_lock);
+
+ 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;
+ int result = 0;
+
+ inthld = CM_CH0_INT_EN << rec->ch;
+ chen = CM_CHEN0 << rec->ch;
+ reset = CM_RST_CH0 << rec->ch;
+ pause = CM_PAUSE0 << rec->ch;
+
+ spin_lock(&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:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&cm->reg_lock);
+ return result;
+}
+
+/*
+ * 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;
+
+ spin_lock_irq(&chip->reg_lock);
+ for (i = 0; i < 4; i++)
+ ucontrol->value.iec958.status[i] = (chip->dig_status >> (i * 8)) & 0xff;
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_irq(&chip->reg_lock);
+ for (i = 0; i < 4; i++)
+ ucontrol->value.iec958.status[i] = (chip->dig_pcm_status >> (i * 8)) & 0xff;
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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(sizeof(*val), GFP_KERNEL);
+ 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(sizeof(*val), GFP_KERNEL);
+ 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)
+ if ((err = save_mixer_state(cm)) < 0)
+ return err;
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&cm->reg_lock);
+ 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;
+ if ((err = setup_spdif_playback(cm, substream, do_spdif, do_ac3)) < 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 */
+ if ((err = setup_spdif_playback(cm, substream, 1, do_ac3)) < 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);
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+
+ msleep(1);
+
+ /* stop and reset stream */
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+
+ 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);
+
+ spin_lock_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);
+
+ spin_unlock_irq(&cm->reg_lock);
+
+ 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);
+
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+
+ 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 */
+ spin_lock(&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);
+ spin_unlock(&cm->reg_lock);
+
+ 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,
+};
+
+static const unsigned int rate_constraints[] = { 5512, 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200, 96000, 128000 };
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+ .count = ARRAY_SIZE(rate_constraints),
+ .list = rate_constraints,
+ .mask = 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..
+ */
+ mutex_lock(&cm->open_mutex);
+ if (cm->opened[ch]) {
+ mutex_unlock(&cm->open_mutex);
+ 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;
+ spin_lock_irq(&cm->reg_lock);
+ snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC);
+ spin_unlock_irq(&cm->reg_lock);
+ }
+ mutex_unlock(&cm->open_mutex);
+ return 0;
+}
+
+static void close_device_check(struct cmipci *cm, int mode)
+{
+ int ch = mode & CM_OPEN_CH_MASK;
+
+ mutex_lock(&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;
+ spin_lock_irq(&cm->reg_lock);
+ snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC);
+ spin_unlock_irq(&cm->reg_lock);
+ }
+ }
+ mutex_unlock(&cm->open_mutex);
+}
+
+/*
+ */
+
+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;
+
+ if ((err = open_device_check(cm, CM_OPEN_PLAYBACK, substream)) < 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) {
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+ if (err < 0)
+ return err;
+ runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+ 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;
+
+ if ((err = open_device_check(cm, CM_OPEN_CAPTURE, substream)) < 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) {
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+ if (err < 0)
+ return err;
+ runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+ 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;
+
+ if ((err = open_device_check(cm, CM_OPEN_PLAYBACK2, substream)) < 0) /* use channel B */
+ return err;
+ runtime->hw = snd_cmipci_playback2;
+ mutex_lock(&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);
+ }
+ }
+ mutex_unlock(&cm->open_mutex);
+ 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) {
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+ if (err < 0)
+ return err;
+ runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+ 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;
+
+ if ((err = open_device_check(cm, CM_OPEN_SPDIF_PLAYBACK, substream)) < 0) /* use channel A */
+ 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;
+
+ if ((err = open_device_check(cm, CM_OPEN_SPDIF_CAPTURE, substream)) < 0) /* use channel B */
+ 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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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(&reg, 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(&reg, kcontrol->private_value);
+ spin_lock_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;
+ }
+ spin_unlock_irq(&cm->reg_lock);
+ 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(&reg, 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;
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+ 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(&reg, kcontrol->private_value);
+ spin_lock_irq(&cm->reg_lock);
+ val1 = snd_cmipci_mixer_read(cm, reg.left_reg);
+ val2 = snd_cmipci_mixer_read(cm, reg.right_reg);
+ spin_unlock_irq(&cm->reg_lock);
+ 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(&reg, kcontrol->private_value);
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+ 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(&reg, 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(&reg, kcontrol->private_value);
+ spin_lock_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;
+ }
+ spin_unlock_irq(&cm->reg_lock);
+ 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(&reg, kcontrol->private_value);
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+ 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);
+
+ spin_lock_irq(&cm->reg_lock);
+ if (args->ac3_sensitive && cm->mixer_insensitive) {
+ ucontrol->value.integer.value[0] = 0;
+ spin_unlock_irq(&cm->reg_lock);
+ 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;
+ spin_unlock_irq(&cm->reg_lock);
+ 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);
+
+ spin_lock_irq(&cm->reg_lock);
+ if (args->ac3_sensitive && cm->mixer_insensitive) {
+ /* ignored */
+ spin_unlock_irq(&cm->reg_lock);
+ 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);
+ }
+ spin_unlock_irq(&cm->reg_lock);
+ 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);
+
+ spin_lock_irq(&cm->reg_lock);
+ ucontrol->value.enumerated.item[0] = get_line_in_mode(cm);
+ spin_unlock_irq(&cm->reg_lock);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+ 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 */
+ spin_lock_irq(&cm->reg_lock);
+ ucontrol->value.enumerated.item[0] =
+ (snd_cmipci_read_b(cm, CM_REG_MISC) & CM_SPDIF_INVERSE) ? 1 : 0;
+ spin_unlock_irq(&cm->reg_lock);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&cm->reg_lock);
+ 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;
+
+ strcpy(card->mixername, "CMedia PCI");
+
+ spin_lock_irq(&cm->reg_lock);
+ snd_cmipci_mixer_write(cm, 0x00, 0x00); /* mixer reset */
+ spin_unlock_irq(&cm->reg_lock);
+
+ 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;
+ }
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cmipci_mixers[idx], cm))) < 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) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_cmipci_spdif_default, cm))) < 0)
+ return err;
+ kctl->id.device = pcm_spdif_device;
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_cmipci_spdif_mask, cm))) < 0)
+ return err;
+ kctl->id.device = pcm_spdif_device;
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_cmipci_spdif_stream, cm))) < 0)
+ return err;
+ kctl->id.device = pcm_spdif_device;
+ }
+ 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_ctl_elem_id elem_id;
+ struct snd_kcontrol *ctl;
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(elem_id.name, cm_saved_mixer[idx].name);
+ ctl = snd_ctl_find_id(cm->card, &elem_id);
+ 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), 0},
+ {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338B), 0},
+ {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738), 0},
+ {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738B), 0},
+ {PCI_VDEVICE(AL, PCI_DEVICE_ID_CMEDIA_CM8738), 0},
+ {0,},
+};
+
+
+/*
+ * 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 = request_region(io_port, 1, "CMIPCI gameport");
+ if (r)
+ break;
+ }
+ } else {
+ io_port = joystick_port[dev];
+ r = request_region(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");
+ release_and_free_resource(r);
+ 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;
+ gameport_set_port_data(gp, r);
+
+ 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) {
+ struct resource *r = gameport_get_port_data(cm->gameport);
+
+ gameport_unregister_port(cm->gameport);
+ cm->gameport = NULL;
+
+ snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN);
+ release_and_free_resource(r);
+ }
+}
+#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 int snd_cmipci_free(struct cmipci *cm)
+{
+ if (cm->irq >= 0) {
+ 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);
+
+ free_irq(cm->irq, cm);
+ }
+
+ snd_cmipci_free_gameport(cm);
+ pci_release_regions(cm->pci);
+ pci_disable_device(cm->pci);
+ kfree(cm);
+ return 0;
+}
+
+static int snd_cmipci_dev_free(struct snd_device *device)
+{
+ struct cmipci *cm = device->device_data;
+ return snd_cmipci_free(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;
+ }
+ }
+ if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 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 **rcmipci)
+{
+ struct cmipci *cm;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_cmipci_dev_free,
+ };
+ 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) },
+ { },
+ };
+
+ *rcmipci = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ cm = kzalloc(sizeof(*cm), GFP_KERNEL);
+ if (cm == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ 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 */
+
+ if ((err = pci_request_regions(pci, card->driver)) < 0) {
+ kfree(cm);
+ pci_disable_device(pci);
+ return err;
+ }
+ cm->iobase = pci_resource_start(pci, 0);
+
+ if (request_irq(pci->irq, snd_cmipci_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, cm)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_cmipci_free(cm);
+ return -EBUSY;
+ }
+ cm->irq = pci->irq;
+ card->sync_irq = cm->irq;
+
+ 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)
+ sprintf(cm->card->driver + strlen(cm->card->driver),
+ "-MC%d", cm->max_channels);
+ else if (cm->can_ac3_sw)
+ strcpy(cm->card->driver + strlen(cm->card->driver), "-SWIEC");
+
+ 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%d", val);
+ if (cm->chip_version < 68)
+ sprintf(modelstr, " (model %d)", cm->chip_version);
+ else
+ modelstr[0] = '\0';
+ sprintf(card->longname, "%s%s at %#lx, irq %i",
+ card->shortname, modelstr, cm->iobase, cm->irq);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, cm, &ops)) < 0) {
+ snd_cmipci_free(cm);
+ return err;
+ }
+
+ 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;
+ if ((err = snd_cmipci_pcm_new(cm, pcm_index)) < 0)
+ return err;
+ pcm_index++;
+ if ((err = snd_cmipci_pcm2_new(cm, pcm_index)) < 0)
+ return err;
+ pcm_index++;
+ if (cm->can_ac3_hw || cm->can_ac3_sw) {
+ pcm_spdif_index = pcm_index;
+ if ((err = snd_cmipci_pcm_spdif_new(cm, pcm_index)) < 0)
+ return err;
+ }
+
+ /* create mixer interface & switches */
+ if ((err = snd_cmipci_mixer_new(cm, pcm_spdif_index)) < 0)
+ return err;
+
+ if (iomidi > 0) {
+ if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI,
+ iomidi,
+ (integrated_midi ?
+ MPU401_INFO_INTEGRATED : 0) |
+ MPU401_INFO_IRQ_HOOK,
+ -1, &cm->rmidi)) < 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);
+
+ *rcmipci = cm;
+ 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;
+ struct cmipci *cm;
+ 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 < 0)
+ return err;
+
+ switch (pci->device) {
+ case PCI_DEVICE_ID_CMEDIA_CM8738:
+ case PCI_DEVICE_ID_CMEDIA_CM8738B:
+ strcpy(card->driver, "CMI8738");
+ break;
+ case PCI_DEVICE_ID_CMEDIA_CM8338A:
+ case PCI_DEVICE_ID_CMEDIA_CM8338B:
+ strcpy(card->driver, "CMI8338");
+ break;
+ default:
+ strcpy(card->driver, "CMIPCI");
+ break;
+ }
+
+ err = snd_cmipci_create(card, pci, dev, &cm);
+ if (err < 0)
+ goto free_card;
+
+ card->private_data = cm;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto free_card;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+free_card:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_cmipci_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(snd_cmipci_pm, snd_cmipci_suspend, snd_cmipci_resume);
+#define SND_CMIPCI_PM_OPS &snd_cmipci_pm
+#else
+#define SND_CMIPCI_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver cmipci_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_cmipci_ids,
+ .probe = snd_cmipci_probe,
+ .remove = snd_cmipci_remove,
+ .driver = {
+ .pm = SND_CMIPCI_PM_OPS,
+ },
+};
+
+module_pci_driver(cmipci_driver);
diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c
new file mode 100644
index 000000000..94d2a6a46
--- /dev/null
+++ b/sound/pci/cs4281.c
@@ -0,0 +1,2046 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Cirrus Logic CS4281 based PCI soundcard
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#include <sound/tlv.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Cirrus Logic CS4281");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Cirrus Logic,CS4281}}");
+
+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;
+
+#ifdef CONFIG_PM_SLEEP
+ u32 suspend_regs[SUSPEND_REGISTERS];
+#endif
+
+};
+
+static irqreturn_t snd_cs4281_interrupt(int irq, void *dev_id);
+
+static const struct pci_device_id snd_cs4281_ids[] = {
+ { PCI_VDEVICE(CIRRUS, 0x6005), 0, }, /* CS4281 */
+ { 0, }
+};
+
+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);
+
+ spin_lock(&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:
+ spin_unlock(&chip->reg_lock);
+ 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);
+ spin_unlock(&chip->reg_lock);
+ 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);
+
+ spin_lock_irq(&chip->reg_lock);
+ snd_cs4281_mode(chip, dma, runtime, 0, 1);
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_irq(&chip->reg_lock);
+ snd_cs4281_mode(chip, dma, runtime, 1, 1);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ strcpy(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,
+ };
+
+ if ((err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus)) < 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;
+ if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+ return err;
+ if (chip->dual_codec) {
+ ac97.num = 1;
+ if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97_secondary)) < 0)
+ return err;
+ }
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4281_fm_vol, chip))) < 0)
+ return err;
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4281_pcm_vol, chip))) < 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 int snd_cs4281_free(struct cs4281 *chip)
+{
+ 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);
+ /* PCI interface - D3 state */
+ pci_set_power_state(chip->pci, PCI_D3hot);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ iounmap(chip->ba0);
+ iounmap(chip->ba1);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+
+ kfree(chip);
+ return 0;
+}
+
+static int snd_cs4281_dev_free(struct snd_device *device)
+{
+ struct cs4281 *chip = device->device_data;
+ return snd_cs4281_free(chip);
+}
+
+static int snd_cs4281_chip_init(struct cs4281 *chip); /* defined below */
+
+static int snd_cs4281_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct cs4281 **rchip,
+ int dual_codec)
+{
+ struct cs4281 *chip;
+ unsigned int tmp;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_cs4281_dev_free,
+ };
+
+ *rchip = NULL;
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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;
+
+ if ((err = pci_request_regions(pci, "CS4281")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->ba0_addr = pci_resource_start(pci, 0);
+ chip->ba1_addr = pci_resource_start(pci, 1);
+
+ chip->ba0 = pci_ioremap_bar(pci, 0);
+ chip->ba1 = pci_ioremap_bar(pci, 1);
+ if (!chip->ba0 || !chip->ba1) {
+ snd_cs4281_free(chip);
+ return -ENOMEM;
+ }
+
+ if (request_irq(pci->irq, snd_cs4281_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_cs4281_free(chip);
+ return -ENOMEM;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ tmp = snd_cs4281_chip_init(chip);
+ if (tmp) {
+ snd_cs4281_free(chip);
+ return tmp;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_cs4281_free(chip);
+ return err;
+ }
+
+ snd_cs4281_proc_init(chip);
+
+ *rchip = 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);
+
+ if ((tmp = snd_cs4281_peekBA0(chip, BA0_SERC1)) != (BA0_SERC1_SO1EN | BA0_SERC1_AC97)) {
+ dev_err(chip->card->dev,
+ "SERC1 AC'97 check failed (0x%x)\n", tmp);
+ return -EIO;
+ }
+ if ((tmp = snd_cs4281_peekBA0(chip, BA0_SERC2)) != (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;
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_cs4281_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ struct cs4281 *chip = substream->rmidi->private_data;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_cs4281_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ struct cs4281 *chip = substream->rmidi->private_data;
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_cs4281_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct cs4281 *chip = substream->rmidi->private_data;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static void snd_cs4281_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct cs4281 *chip = substream->rmidi->private_data;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ }
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4281_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct cs4281 *chip = substream->rmidi->private_data;
+ unsigned char byte;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ }
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+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;
+
+ if ((err = snd_rawmidi_new(chip->card, "CS4281", device, 1, 1, &rmidi)) < 0)
+ return err;
+ strcpy(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++)
+ if (status & BA0_HISR_DMA(dma)) {
+ cdma = &chip->dma[dma];
+ spin_lock(&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++;
+ spin_unlock(&chip->reg_lock);
+ continue;
+ }
+ if ((val & BA0_HDSR_DTC) && (cdma->frag & 1)) {
+ cdma->frag--;
+ chip->spurious_dtc_irq++;
+ spin_unlock(&chip->reg_lock);
+ continue;
+ }
+ spin_unlock(&chip->reg_lock);
+ snd_pcm_period_elapsed(cdma->substream);
+ }
+ }
+
+ if ((status & BA0_HISR_MIDI) && chip->rmidi) {
+ unsigned char c;
+
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+ }
+
+ /* 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)
+{
+ unsigned long flags;
+ 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 */
+
+ spin_lock_irqsave(&opl3->reg_lock, flags);
+
+ writel((unsigned int)cmd, port);
+ udelay(10);
+
+ writel((unsigned int)val, port + 4);
+ udelay(30);
+
+ spin_unlock_irqrestore(&opl3->reg_lock, flags);
+}
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if ((err = snd_cs4281_create(card, pci, &chip, dual_codec[dev])) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ if ((err = snd_cs4281_mixer(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_cs4281_pcm(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_cs4281_midi(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_opl3_new(card, OPL3_HW_OPL3_CS4281, &opl3)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ opl3->private_data = chip;
+ opl3->command = snd_cs4281_opl3_command;
+ snd_opl3_init(opl3);
+ if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ snd_cs4281_create_gameport(chip);
+ strcpy(card->driver, "CS4281");
+ strcpy(card->shortname, "Cirrus Logic CS4281");
+ sprintf(card->longname, "%s at 0x%lx, irq %d",
+ card->shortname,
+ chip->ba0_addr,
+ chip->irq);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_cs4281_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+/*
+ * Power Management
+ */
+#ifdef CONFIG_PM_SLEEP
+
+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 SIMPLE_DEV_PM_OPS(cs4281_pm, cs4281_suspend, cs4281_resume);
+#define CS4281_PM_OPS &cs4281_pm
+#else
+#define CS4281_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver cs4281_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_cs4281_ids,
+ .probe = snd_cs4281_probe,
+ .remove = snd_cs4281_remove,
+ .driver = {
+ .pm = CS4281_PM_OPS,
+ },
+};
+
+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 <perex@perex.cz>
+#
+
+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..a6e0a4439
--- /dev/null
+++ b/sound/pci/cs46xx/cs46xx.c
@@ -0,0 +1,161 @@
+// 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 <perex@perex.cz>
+ */
+
+/*
+ NOTES:
+ - sometimes the sound is metallic and sibilant, unloading and
+ reloading the module may solve this.
+*/
+
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include "cs46xx.h"
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Cirrus Logic Sound Fusion CS46XX");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Cirrus Logic,Sound Fusion (CS4280)},"
+ "{Cirrus Logic,Sound Fusion (CS4610)},"
+ "{Cirrus Logic,Sound Fusion (CS4612)},"
+ "{Cirrus Logic,Sound Fusion (CS4615)},"
+ "{Cirrus Logic,Sound Fusion (CS4622)},"
+ "{Cirrus Logic,Sound Fusion (CS4624)},"
+ "{Cirrus Logic,Sound Fusion (CS4630)}}");
+
+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), 0, }, /* CS4280 */
+ { PCI_VDEVICE(CIRRUS, 0x6003), 0, }, /* CS4612 */
+ { PCI_VDEVICE(CIRRUS, 0x6004), 0, }, /* CS4615 */
+ { 0, }
+};
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+ if ((err = snd_cs46xx_create(card, pci,
+ external_amp[dev], thinkpad[dev],
+ &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+ chip->accept_valid = mmap_valid[dev];
+ if ((err = snd_cs46xx_pcm(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ if ((err = snd_cs46xx_pcm_rear(chip, 1)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_cs46xx_pcm_iec958(chip, 2)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#endif
+ if ((err = snd_cs46xx_mixer(chip, 2)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ if (chip->nr_ac97_codecs ==2) {
+ if ((err = snd_cs46xx_pcm_center_lfe(chip, 3)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+#endif
+ if ((err = snd_cs46xx_midi(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_cs46xx_start_dsp(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+
+ snd_cs46xx_gameport(chip);
+
+ strcpy(card->driver, "CS46xx");
+ strcpy(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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_card_cs46xx_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver cs46xx_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_cs46xx_ids,
+ .probe = snd_card_cs46xx_probe,
+ .remove = snd_card_cs46xx_remove,
+#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..b275df883
--- /dev/null
+++ b/sound/pci/cs46xx/cs46xx.h
@@ -0,0 +1,1734 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_CS46XX_H
+#define __SOUND_CS46XX_H
+
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ * Cirrus Logic, Inc.
+ * Definitions for Cirrus Logic CS46xx chips
+ */
+
+#include <sound/pcm.h>
+#include <sound/pcm-indirect.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#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 resource *resource;
+};
+
+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,
+ struct snd_cs46xx **rcodec);
+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 <perex@perex.cz>
+ *
+ * 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 <asm/byteorder.h>
+
+#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 <perex@perex.cz>
+ */
+
+#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 <perex@perex.cz>
+ *
+ * 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..4490dd746
--- /dev/null
+++ b/sound/pci/cs46xx/cs46xx_lib.c
@@ -0,0 +1,4029 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Abramo Bagnara <abramo@alsa-project.org>
+ * 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 <benny@hostmobility.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/mutex.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/vmalloc.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#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;
+ 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) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ err = -ENOMEM;
+ module = kzalloc(sizeof(*module), GFP_KERNEL);
+ 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 =
+ kcalloc(nums, sizeof(struct dsp_symbol_entry), GFP_KERNEL);
+ 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 =
+ kcalloc(nums, sizeof(struct dsp_segment_desc), GFP_KERNEL);
+ 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;
+ release_firmware(fw);
+ return 0;
+
+ error_inval:
+ err = -EINVAL;
+ error:
+ free_module_desc(module);
+ release_firmware(fw);
+ 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;
+ int i, size, err;
+
+ err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev);
+ if (err < 0)
+ return err;
+ if (fw->size != sizeof(*chip->ba1)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ chip->ba1 = vmalloc(sizeof(*chip->ba1));
+ if (!chip->ba1) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ 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)
+ err = -EINVAL;
+
+ error:
+ release_firmware(fw);
+ return err;
+}
+
+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 long flags;
+ 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.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_cs46xx_poke(chip, BA1_PSRC,
+ ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
+ snd_cs46xx_poke(chip, BA1_PPI, phiIncr);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs46xx_set_capture_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
+{
+ unsigned long flags;
+ 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 = ((48000 * 24) + rate - 1) / rate;
+
+ /*
+ * Fill in the VariDecimate control block.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ /*
+ * Figure out the frame group length for the write back task. Basically,
+ * this is just the factors of 24000 (2^6*3*5^3) that are not present in
+ * the output sample rate.
+ */
+ frameGroupLength = 1;
+ for (cnt = 2; cnt <= 64; cnt *= 2) {
+ if (((rate / cnt) * cnt) != rate)
+ frameGroupLength *= 2;
+ }
+ if (((rate / 3) * 3) != rate) {
+ frameGroupLength *= 3;
+ }
+ for (cnt = 5; cnt <= 125; cnt *= 5) {
+ if (((rate / cnt) * cnt) != rate)
+ frameGroupLength *= 5;
+ }
+
+ /*
+ * Fill in the WriteBack control block.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ * 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
+ spin_lock(&chip->reg_lock);
+ if (substream->runtime->periods != CS46XX_FRAGS)
+ snd_cs46xx_playback_transfer(substream);
+ { unsigned int tmp;
+ tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+ tmp &= 0x0000ffff;
+ snd_cs46xx_poke(chip, BA1_PCTL, chip->play_ctl | tmp);
+ }
+ spin_unlock(&chip->reg_lock);
+#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
+ spin_lock(&chip->reg_lock);
+ { unsigned int tmp;
+ tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+ tmp &= 0x0000ffff;
+ snd_cs46xx_poke(chip, BA1_PCTL, tmp);
+ }
+ spin_unlock(&chip->reg_lock);
+#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;
+ int result = 0;
+
+ spin_lock(&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:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&chip->reg_lock);
+
+ return result;
+}
+
+#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);
+
+ if ( (cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate, cpcm,
+ cpcm->hw_buf.addr,
+ cpcm->pcm_channel_id)) == NULL) {
+ 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;
+
+ mutex_lock(&chip->spos_mutex);
+
+ if (_cs46xx_adjust_sample_rate (chip,cpcm,sample_rate)) {
+ mutex_unlock(&chip->spos_mutex);
+ return -ENXIO;
+ }
+
+ snd_BUG_ON(!cpcm->pcm_channel);
+ if (!cpcm->pcm_channel) {
+ mutex_unlock(&chip->spos_mutex);
+ return -ENXIO;
+ }
+
+
+ if (cs46xx_dsp_pcm_channel_set_period (chip,cpcm->pcm_channel,period_size)) {
+ mutex_unlock(&chip->spos_mutex);
+ 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);
+ runtime->dma_area = cpcm->hw_buf.area;
+ runtime->dma_addr = cpcm->hw_buf.addr;
+ runtime->dma_bytes = cpcm->hw_buf.bytes;
+
+
+#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) {
+ runtime->dma_area = NULL;
+ runtime->dma_addr = 0;
+ runtime->dma_bytes = 0;
+ }
+ if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) {
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ mutex_unlock(&chip->spos_mutex);
+#endif
+ 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
+
+ }
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ mutex_unlock(&chip->spos_mutex);
+#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);
+
+ runtime->dma_area = NULL;
+ runtime->dma_addr = 0;
+ runtime->dma_bytes = 0;
+
+ 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);
+ runtime->dma_area = chip->capt.hw_buf.area;
+ runtime->dma_addr = chip->capt.hw_buf.addr;
+ runtime->dma_bytes = chip->capt.hw_buf.bytes;
+ substream->ops = &snd_cs46xx_capture_ops;
+ } else {
+ if (runtime->dma_area == chip->capt.hw_buf.area) {
+ runtime->dma_area = NULL;
+ runtime->dma_addr = 0;
+ runtime->dma_bytes = 0;
+ }
+ if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 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);
+ runtime->dma_area = NULL;
+ runtime->dma_addr = 0;
+ runtime->dma_bytes = 0;
+
+ 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;
+
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+ }
+ /*
+ * 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
+ mutex_lock(&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);
+
+ mutex_unlock(&chip->spos_mutex);
+#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");
+
+ mutex_lock(&chip->spos_mutex);
+ cs46xx_iec958_pre_open (chip);
+ mutex_unlock(&chip->spos_mutex);
+
+ 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);
+
+ mutex_lock(&chip->spos_mutex);
+ cs46xx_iec958_post_close (chip);
+ mutex_unlock(&chip->spos_mutex);
+
+ 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
+ mutex_lock(&chip->spos_mutex);
+ if (cpcm->pcm_channel) {
+ cs46xx_dsp_destroy_pcm_channel(chip,cpcm->pcm_channel);
+ cpcm->pcm_channel = NULL;
+ }
+ mutex_unlock(&chip->spos_mutex);
+#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;
+
+ if ((err = snd_pcm_new(chip->card, "CS46xx", device, MAX_PLAYBACK_CHANNELS, 1, &pcm)) < 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;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(chip->card, "CS46xx - Rear", device, MAX_PLAYBACK_CHANNELS, 0, &pcm)) < 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;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(chip->card, "CS46xx - Center LFE", device, MAX_PLAYBACK_CHANNELS, 0, &pcm)) < 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;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(chip->card, "CS46xx - IEC958", device, 1, 0, &pcm)) < 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;
+ strcpy(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_bus(struct snd_ac97_bus *bus)
+{
+ struct snd_cs46xx *chip = bus->private_data;
+
+ chip->ac97_bus = NULL;
+}
+
+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:
+ mutex_lock(&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));
+ mutex_unlock(&chip->spos_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&chip->spos_mutex);
+
+ 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);
+ if ((err = snd_ac97_read(ac97, AC97_REC_GAIN)) == 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;
+ struct snd_ctl_elem_id id;
+ 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");
+ if ((err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+ return err;
+ chip->ac97_bus->private_free = snd_cs46xx_mixer_free_ac97_bus;
+
+ 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;
+ if ((err = snd_ctl_add(card, kctl)) < 0)
+ return err;
+ }
+
+ /* get EAPD mixer switch (for voyetra hack) */
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(id.name, "External Amplifier");
+ chip->eapd_switch = snd_ctl_find_id(chip->card, &id);
+
+#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);
+ spin_lock_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);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_cs46xx_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_cs46xx_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ chip->active_ctrl(chip, -1);
+ return 0;
+}
+
+static void snd_cs46xx_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ }
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs46xx_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct snd_cs46xx *chip = substream->rmidi->private_data;
+ unsigned char byte;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ }
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+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;
+
+ if ((err = snd_rawmidi_new(chip->card, "CS46XX", device, 1, 1, &rmidi)) < 0)
+ return err;
+ strcpy(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 int snd_cs46xx_free(struct snd_cs46xx *chip)
+{
+ int idx;
+
+ if (snd_BUG_ON(!chip))
+ return -EINVAL;
+
+ 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);
+
+ if (chip->region.idx[0].resource)
+ snd_cs46xx_hw_stop(chip);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ if (chip->active_ctrl)
+ chip->active_ctrl(chip, -chip->amplifier);
+
+ for (idx = 0; idx < 5; idx++) {
+ struct snd_cs46xx_region *region = &chip->region.idx[idx];
+
+ iounmap(region->remap_addr);
+ release_and_free_resource(region->resource);
+ }
+
+#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
+
+#ifdef CONFIG_PM_SLEEP
+ kfree(chip->saved_regs);
+#endif
+
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_cs46xx_dev_free(struct snd_device *device)
+{
+ struct snd_cs46xx *chip = device->device_data;
+ return snd_cs46xx_free(chip);
+}
+
+/*
+ * 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);
+ if ((err = snd_ctl_add(card, kctl)) < 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 **rchip)
+{
+ struct snd_cs46xx *chip;
+ int err, idx;
+ struct snd_cs46xx_region *region;
+ struct cs_card_type *cp;
+ u16 ss_card, ss_vendor;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_cs46xx_dev_free,
+ };
+
+ *rchip = NULL;
+
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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;
+ 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);
+ snd_cs46xx_free(chip);
+ return -ENOMEM;
+ }
+
+ region = &chip->region.name.ba0;
+ strcpy(region->name, "CS46xx_BA0");
+ region->base = chip->ba0_addr;
+ region->size = CS46XX_BA0_SIZE;
+
+ region = &chip->region.name.data0;
+ strcpy(region->name, "CS46xx_BA1_data0");
+ region->base = chip->ba1_addr + BA1_SP_DMEM0;
+ region->size = CS46XX_BA1_DATA0_SIZE;
+
+ region = &chip->region.name.data1;
+ strcpy(region->name, "CS46xx_BA1_data1");
+ region->base = chip->ba1_addr + BA1_SP_DMEM1;
+ region->size = CS46XX_BA1_DATA1_SIZE;
+
+ region = &chip->region.name.pmem;
+ strcpy(region->name, "CS46xx_BA1_pmem");
+ region->base = chip->ba1_addr + BA1_SP_PMEM;
+ region->size = CS46XX_BA1_PRG_SIZE;
+
+ region = &chip->region.name.reg;
+ strcpy(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];
+ if ((region->resource = request_mem_region(region->base, region->size,
+ region->name)) == NULL) {
+ dev_err(chip->card->dev,
+ "unable to request memory region 0x%lx-0x%lx\n",
+ region->base, region->base + region->size - 1);
+ snd_cs46xx_free(chip);
+ return -EBUSY;
+ }
+ region->remap_addr = ioremap(region->base, region->size);
+ if (region->remap_addr == NULL) {
+ dev_err(chip->card->dev,
+ "%s ioremap problem\n", region->name);
+ snd_cs46xx_free(chip);
+ return -ENOMEM;
+ }
+ }
+
+ if (request_irq(pci->irq, snd_cs46xx_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_cs46xx_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ chip->dsp_spos_instance = cs46xx_dsp_spos_create(chip);
+ if (chip->dsp_spos_instance == NULL) {
+ snd_cs46xx_free(chip);
+ return -ENOMEM;
+ }
+#endif
+
+ err = snd_cs46xx_chip_init(chip);
+ if (err < 0) {
+ snd_cs46xx_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_cs46xx_free(chip);
+ return err;
+ }
+
+ snd_cs46xx_proc_init(card, chip);
+
+#ifdef CONFIG_PM_SLEEP
+ chip->saved_regs = kmalloc_array(ARRAY_SIZE(saved_regs),
+ sizeof(*chip->saved_regs),
+ GFP_KERNEL);
+ if (!chip->saved_regs) {
+ snd_cs46xx_free(chip);
+ return -ENOMEM;
+ }
+#endif
+
+ chip->active_ctrl(chip, -1); /* disable CLKRUN */
+
+ *rchip = chip;
+ 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 <perex@perex.cz>
+ */
+
+#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..05f3f6dc9
--- /dev/null
+++ b/sound/pci/cs46xx/dsp_spos.c
@@ -0,0 +1,1982 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ */
+
+/*
+ * 2002-07 Benny Sjostrand benny@hostmobility.com
+ */
+
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#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);
+
+ strcpy (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(sizeof(struct dsp_spos_instance), GFP_KERNEL);
+
+ 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_array(DSP_MAX_MODULES,
+ sizeof(struct dsp_module_desc),
+ GFP_KERNEL);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->spos_mutex);
+}
+
+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;
+
+ mutex_lock(&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);
+ }
+ }
+ mutex_unlock(&chip->spos_mutex);
+}
+
+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;
+
+ mutex_lock(&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");
+ mutex_unlock(&chip->spos_mutex);
+}
+
+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;
+
+ mutex_lock(&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");
+ mutex_unlock(&chip->spos_mutex);
+}
+
+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;
+ }
+
+ if ( (symbol = cs46xx_dsp_lookup_symbol_addr (chip,i / sizeof(u32), SYMBOL_PARAMETER)) != NULL) {
+ 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);
+
+ mutex_lock(&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));
+ }
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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) );
+ }
+ mutex_unlock(&chip->spos_mutex);
+
+ 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]));
+ strcpy(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)
+ strcpy(ins->tasks[ins->ntask].task_name, name);
+ else
+ strcpy(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;
+
+ mutex_lock(&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);
+
+ spin_lock_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);
+
+ spin_unlock_irq(&chip->reg_lock);
+
+ /* 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;
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&chip->spos_mutex);
+
+ /* 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;
+
+ mutex_lock(&chip->spos_mutex);
+ ins->pcm_input = cs46xx_add_record_source(chip,ins->ref_snoop_scb,PCMSERIALIN_PCM_SCB_ADDR,
+ "PCMSerialInput_Wave");
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&chip->spos_mutex);
+ cs46xx_dsp_remove_scb (chip,ins->pcm_input);
+ ins->pcm_input = NULL;
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&chip->spos_mutex);
+ ins->adc_input = cs46xx_add_record_source(chip,ins->codec_in_scb,PCMSERIALIN_SCB_ADDR,
+ "PCMSerialInput_ADC");
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&chip->spos_mutex);
+ cs46xx_dsp_remove_scb (chip,ins->adc_input);
+ ins->adc_input = NULL;
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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;
+
+ mutex_unlock(&chip->spos_mutex);
+
+ 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;
+
+ mutex_lock(&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;
+
+ mutex_unlock(&chip->spos_mutex);
+
+ 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 <perex@perex.cz>
+ */
+
+/*
+ * 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..1f90ca723
--- /dev/null
+++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c
@@ -0,0 +1,1777 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ */
+
+/*
+ * 2002-07 Benny Sjostrand benny@hostmobility.com
+ */
+
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->spos_mutex);
+}
+#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;
+ unsigned long flags;
+
+ /* 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
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ _dsp_unlink_scb (chip,scb);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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;
+ }
+
+#if 0
+ /* !!!! THIS IS A PIECE OF SHIT MADE BY ME !!! */
+ for(i = scb->index + 1;i < ins->nscb; ++i) {
+ ins->scbs[i - 1].index = i - 1;
+ }
+#endif
+}
+
+
+#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(sizeof(struct proc_scb_info), GFP_KERNEL);
+ 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;
+
+ unsigned long flags;
+
+ 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();
+ }
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+
+ /* update entry in DSP RAM */
+ cs46xx_dsp_spos_update_scb(chip,scb->parent_scb_ptr);
+
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ }
+
+
+ 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;
+ unsigned long flags;
+
+ 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;
+ }
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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 ++;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!pcm_channel->active ||
+ ins->npcm_channels <= 0 ||
+ pcm_channel->src_scb->ref_count <= 0))
+ return;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ pcm_channel->unlinked = 1;
+ pcm_channel->active = 0;
+ pcm_channel->private_data = NULL;
+ pcm_channel->src_scb->ref_count --;
+ ins->npcm_channels --;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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)
+{
+ unsigned long flags;
+
+ if (snd_BUG_ON(!pcm_channel->active ||
+ chip->dsp_spos_instance->npcm_channels <= 0))
+ return -EIO;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (pcm_channel->unlinked) {
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return -EIO;
+ }
+
+ pcm_channel->unlinked = 1;
+
+ _dsp_unlink_scb (chip,pcm_channel->pcm_reader_scb);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+
+ if (pcm_channel->unlinked == 0) {
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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)
+{
+ unsigned long flags;
+
+ if (snd_BUG_ON(!src->parent_scb_ptr))
+ return -EINVAL;
+
+ /* mute SCB */
+ cs46xx_dsp_scb_set_volume (chip,src,0,0);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ _dsp_unlink_scb (chip,src);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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..20b4faea5
--- /dev/null
+++ b/sound/pci/cs5530.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio
+ *
+ * (C) Copyright 2007 Ash Willis <ashwillis@programmer.net>
+ * (C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk>
+ *
+ * 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 <linux/delay.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+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 int snd_cs5530_free(struct snd_cs5530 *chip)
+{
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_cs5530_dev_free(struct snd_device *device)
+{
+ struct snd_cs5530 *chip = device->device_data;
+ return snd_cs5530_free(chip);
+}
+
+static void snd_cs5530_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+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 **rchip)
+{
+ struct snd_cs5530 *chip;
+ unsigned long sb_base;
+ u8 irq, dma8, dma16 = 0;
+ u16 map;
+ void __iomem *mem;
+ int err;
+
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_cs5530_dev_free,
+ };
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+ chip->pci = pci;
+
+ err = pci_request_regions(pci, "CS5530");
+ if (err < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->pci_base = pci_resource_start(pci, 0);
+
+ mem = pci_ioremap_bar(pci, 0);
+ if (mem == NULL) {
+ snd_cs5530_free(chip);
+ return -EBUSY;
+ }
+
+ map = readw(mem + 0x18);
+ iounmap(mem);
+
+ /* Map bits
+ 0:1 * 0x20 + 0x200 = sb base
+ 2 sb enable
+ 3 adlib enable
+ 5 MPU enable 0x330
+ 6 MPU enable 0x300
+
+ The other bits may be used internally so must be masked */
+
+ sb_base = 0x220 + 0x20 * (map & 3);
+
+ if (map & (1<<2))
+ dev_info(card->dev, "XpressAudio at 0x%lx\n", sb_base);
+ else {
+ dev_err(card->dev, "Could not find XpressAudio!\n");
+ snd_cs5530_free(chip);
+ 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");
+ snd_cs5530_free(chip);
+ 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");
+ snd_cs5530_free(chip);
+ return -ENODEV;
+ }
+
+ if (irq & 1)
+ irq = 9;
+ else if (irq & 2)
+ irq = 5;
+ else if (irq & 4)
+ irq = 7;
+ else if (irq & 8)
+ irq = 10;
+ else {
+ dev_err(card->dev, "SoundBlaster IRQ not set\n");
+ snd_cs5530_free(chip);
+ 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");
+ snd_cs5530_free(chip);
+ return err;
+ }
+
+ err = snd_sb16dsp_pcm(chip->sb, 0);
+ if (err < 0) {
+ dev_err(card->dev, "Could not create PCM\n");
+ snd_cs5530_free(chip);
+ return err;
+ }
+
+ err = snd_sbmixer_new(chip->sb);
+ if (err < 0) {
+ dev_err(card->dev, "Could not create Mixer\n");
+ snd_cs5530_free(chip);
+ return err;
+ }
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ snd_cs5530_free(chip);
+ return err;
+ }
+
+ *rchip = chip;
+ 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 = NULL;
+ 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 < 0)
+ return err;
+
+ err = snd_cs5530_create(card, pci, &chip);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ strcpy(card->driver, "CS5530");
+ strcpy(card->shortname, "CS5530 Audio");
+ sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base);
+
+ err = snd_card_register(card);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static struct pci_driver cs5530_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_cs5530_ids,
+ .probe = snd_cs5530_probe,
+ .remove = snd_cs5530_remove,
+};
+
+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..11ce3c458
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio.c
@@ -0,0 +1,397 @@
+// 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 <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#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,
+ };
+
+ if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 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);
+
+ if ((err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97)) < 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;
+ spin_lock(&cs5535au->reg_lock);
+ bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS);
+ spin_unlock(&cs5535au->reg_lock);
+ 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;
+ spin_lock(&cs5535au->reg_lock);
+ bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS);
+ spin_unlock(&cs5535au->reg_lock);
+ 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 int snd_cs5535audio_free(struct cs5535audio *cs5535au)
+{
+ pci_set_power_state(cs5535au->pci, PCI_D3hot);
+
+ if (cs5535au->irq >= 0)
+ free_irq(cs5535au->irq, cs5535au);
+
+ pci_release_regions(cs5535au->pci);
+ pci_disable_device(cs5535au->pci);
+ kfree(cs5535au);
+ return 0;
+}
+
+static int snd_cs5535audio_dev_free(struct snd_device *device)
+{
+ struct cs5535audio *cs5535au = device->device_data;
+ return snd_cs5535audio_free(cs5535au);
+}
+
+static int snd_cs5535audio_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct cs5535audio **rcs5535au)
+{
+ struct cs5535audio *cs5535au;
+
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_cs5535audio_dev_free,
+ };
+
+ *rcs5535au = NULL;
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) {
+ dev_warn(card->dev, "unable to get 32bit dma\n");
+ err = -ENXIO;
+ goto pcifail;
+ }
+
+ cs5535au = kzalloc(sizeof(*cs5535au), GFP_KERNEL);
+ if (cs5535au == NULL) {
+ err = -ENOMEM;
+ goto pcifail;
+ }
+
+ spin_lock_init(&cs5535au->reg_lock);
+ cs5535au->card = card;
+ cs5535au->pci = pci;
+ cs5535au->irq = -1;
+
+ if ((err = pci_request_regions(pci, "CS5535 Audio")) < 0) {
+ kfree(cs5535au);
+ goto pcifail;
+ }
+
+ cs5535au->port = pci_resource_start(pci, 0);
+
+ if (request_irq(pci->irq, snd_cs5535audio_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, cs5535au)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ err = -EBUSY;
+ goto sndfail;
+ }
+
+ cs5535au->irq = pci->irq;
+ card->sync_irq = cs5535au->irq;
+ pci_set_master(pci);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+ cs5535au, &ops)) < 0)
+ goto sndfail;
+
+ *rcs5535au = cs5535au;
+ return 0;
+
+sndfail: /* leave the device alive, just kill the snd */
+ snd_cs5535audio_free(cs5535au);
+ return err;
+
+pcifail:
+ pci_disable_device(pci);
+ return err;
+}
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if ((err = snd_cs5535audio_create(card, pci, &cs5535au)) < 0)
+ goto probefail_out;
+
+ card->private_data = cs5535au;
+
+ if ((err = snd_cs5535audio_mixer(cs5535au)) < 0)
+ goto probefail_out;
+
+ if ((err = snd_cs5535audio_pcm(cs5535au)) < 0)
+ goto probefail_out;
+
+ strcpy(card->driver, DRIVER_NAME);
+
+ strcpy(card->shortname, "CS5535 Audio");
+ sprintf(card->longname, "%s %s at 0x%lx, irq %i",
+ card->shortname, card->driver,
+ cs5535au->port, cs5535au->irq);
+
+ if ((err = snd_card_register(card)) < 0)
+ goto probefail_out;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+probefail_out:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_cs5535audio_remove(struct pci_dev *pci)
+{
+ olpc_quirks_cleanup();
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver cs5535audio_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_cs5535audio_ids,
+ .probe = snd_cs5535audio_probe,
+ .remove = snd_cs5535audio_remove,
+#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");
+MODULE_SUPPORTED_DEVICE("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..4e295303b
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio_olpc.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OLPC XO-1 additional sound features
+ *
+ * Copyright © 2006 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright © 2007-2008 Andres Salomon <dilinger@debian.org>
+ */
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <linux/gpio.h>
+
+#include <asm/olpc.h>
+#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;
+ strlcpy(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;
+ strlcpy(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) {
+ gpio_free(OLPC_GPIO_MIC_AC);
+ return err;
+ }
+ }
+
+ /* turn off the mic by default */
+ olpc_mic_bias(ac97, 0);
+ return 0;
+}
+
+void olpc_quirks_cleanup(void)
+{
+ 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..4032b89b1
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio_pcm.c
@@ -0,0 +1,435 @@
+// 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 <linux/init.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#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]);
+ if ((err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) < 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, desc_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 uppper will be 0 */
+ addr = (u32) substream->runtime->dma_addr;
+ desc_addr = (u32) dma->desc_buf.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);
+ desc_addr += sizeof(struct cs5535audio_dma_desc);
+ 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;
+ spin_lock_irq(&cs5535au->reg_lock);
+ dma->ops->disable_dma(cs5535au);
+ dma->ops->setup_prd(cs5535au, jmpprd_addr);
+ spin_unlock_irq(&cs5535au->reg_lock);
+ 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;
+ int err = 0;
+
+ spin_lock(&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");
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock(&cs5535au->reg_lock);
+ return err;
+}
+
+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]);
+ if ((err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) < 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;
+ strcpy(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 <linux/init.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#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..70888706a
--- /dev/null
+++ b/sound/pci/ctxfi/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-ctxfi-objs := 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..d4bfee499
--- /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..af94ea66f
--- /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..6d636bdca
--- /dev/null
+++ b/sound/pci/ctxfi/ctamixer.c
@@ -0,0 +1,484 @@
+// 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 <linux/slab.h>
+
+#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->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;
+ unsigned long flags;
+
+ *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;
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < desc->msr; i++) {
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+ if (err)
+ break;
+
+ amixer->idx[i] = idx;
+ }
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i--; i >= 0; i--)
+ mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ kfree(amixer);
+ return err;
+}
+
+static int put_amixer_rsc(struct amixer_mgr *mgr, struct amixer *amixer)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < amixer->rsc.msr; i++)
+ mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ amixer_rsc_uninit(amixer);
+ kfree(amixer);
+
+ return 0;
+}
+
+int amixer_mgr_create(struct hw *hw, struct amixer_mgr **ramixer_mgr)
+{
+ int err;
+ struct amixer_mgr *amixer_mgr;
+
+ *ramixer_mgr = NULL;
+ amixer_mgr = kzalloc(sizeof(*amixer_mgr), GFP_KERNEL);
+ 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(struct amixer_mgr *amixer_mgr)
+{
+ 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;
+
+ 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;
+ unsigned long flags;
+
+ *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;
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < desc->msr; i++) {
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+ if (err)
+ break;
+
+ sum->idx[i] = idx;
+ }
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i--; i >= 0; i--)
+ mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ kfree(sum);
+ return err;
+}
+
+static int put_sum_rsc(struct sum_mgr *mgr, struct sum *sum)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < sum->rsc.msr; i++)
+ mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ sum_rsc_uninit(sum);
+ kfree(sum);
+
+ return 0;
+}
+
+int sum_mgr_create(struct hw *hw, struct sum_mgr **rsum_mgr)
+{
+ int err;
+ struct sum_mgr *sum_mgr;
+
+ *rsum_mgr = NULL;
+ sum_mgr = kzalloc(sizeof(*sum_mgr), GFP_KERNEL);
+ 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(struct sum_mgr *sum_mgr)
+{
+ 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..4fafb397a
--- /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 <linux/spinlock.h>
+#include <sound/core.h>
+
+/* 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, struct sum_mgr **rsum_mgr);
+int sum_mgr_destroy(struct sum_mgr *sum_mgr);
+
+/* 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, struct amixer_mgr **ramixer_mgr);
+int amixer_mgr_destroy(struct amixer_mgr *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..06775519d
--- /dev/null
+++ b/sound/pci/ctxfi/ctatc.c
@@ -0,0 +1,1748 @@
+// 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 <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/asoundef.h>
+
+#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_SB1270,
+ "SB1270", CTSB1270),
+ 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_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",
+ [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"}
+};
+
+typedef int (*create_t)(struct hw *, void **);
+typedef int (*destroy_t)(void *);
+
+static struct {
+ int (*create)(struct hw *hw, void **rmgr);
+ int (*destroy)(void *mgr);
+} rsc_mgr_funcs[NUM_RSCTYP] = {
+ [SRC] = { .create = (create_t)src_mgr_create,
+ .destroy = (destroy_t)src_mgr_destroy },
+ [SRCIMP] = { .create = (create_t)srcimp_mgr_create,
+ .destroy = (destroy_t)srcimp_mgr_destroy },
+ [AMIXER] = { .create = (create_t)amixer_mgr_create,
+ .destroy = (destroy_t)amixer_mgr_destroy },
+ [SUM] = { .create = (create_t)sum_mgr_create,
+ .destroy = (destroy_t)sum_mgr_destroy },
+ [DAIO] = { .create = (create_t)daio_mgr_create,
+ .destroy = (destroy_t)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];
+ mutex_lock(&atc->atc_mutex);
+ amixer->ops->setup(amixer, &src->rsc,
+ INIT_VOL, atc->pcm[i+device*2]);
+ mutex_unlock(&atc->atc_mutex);
+ 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, rsr = atc->pll_rate;
+
+ /* 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;
+ }
+
+ mutex_lock(&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);
+ mutex_unlock(&atc->atc_mutex);
+
+ 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 */
+ mutex_lock(&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);
+ mutex_unlock(&atc->atc_mutex);
+
+ 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 struct capabilities atc_capabilities(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+
+ return hw->capabilities(hw);
+}
+
+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_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, SPDIFIO);
+}
+
+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;
+
+ mutex_lock(&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);
+ mutex_unlock(&atc->atc_mutex);
+
+ 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 < atc->n_daio; i++) {
+ daio = atc->daios[i];
+ if (daio->type < LINEIM) {
+ 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 < atc->n_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 < atc->n_src; 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 < atc->n_srcimp; i++) {
+ 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);
+ 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;
+ int err, i, num_srcs, num_daios;
+
+ num_daios = ((atc->model == CTSB1270) ? 8 : 7);
+ num_srcs = ((atc->model == CTSB1270) ? 6 : 4);
+
+ atc->daios = kcalloc(num_daios, sizeof(void *), GFP_KERNEL);
+ if (!atc->daios)
+ return -ENOMEM;
+
+ atc->srcs = kcalloc(num_srcs, sizeof(void *), GFP_KERNEL);
+ if (!atc->srcs)
+ return -ENOMEM;
+
+ atc->srcimps = kcalloc(num_srcs, sizeof(void *), GFP_KERNEL);
+ if (!atc->srcimps)
+ return -ENOMEM;
+
+ atc->pcm = kcalloc(2 * 4, 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, atc->n_daio = 0; i < num_daios; i++) {
+ da_desc.type = (atc->model != CTSB073X) ? i :
+ ((i == SPDIFIO) ? SPDIFI1 : i);
+ 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;
+ }
+ atc->n_daio++;
+ }
+
+ src_mgr = atc->rsc_mgrs[SRC];
+ src_dsc.multi = 1;
+ src_dsc.msr = atc->msr;
+ src_dsc.mode = ARCRW;
+ for (i = 0, atc->n_src = 0; i < num_srcs; i++) {
+ err = src_mgr->get_src(src_mgr, &src_dsc,
+ (struct src **)&atc->srcs[i]);
+ if (err)
+ return err;
+
+ atc->n_src++;
+ }
+
+ srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+ srcimp_dsc.msr = 8;
+ for (i = 0, atc->n_srcimp = 0; i < num_srcs; i++) {
+ err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc,
+ (struct srcimp **)&atc->srcimps[i]);
+ if (err)
+ return err;
+
+ atc->n_srcimp++;
+ }
+
+ sum_mgr = atc->rsc_mgrs[SUM];
+ sum_dsc.msr = atc->msr;
+ for (i = 0, atc->n_pcm = 0; i < (2*4); i++) {
+ err = sum_mgr->get_sum(sum_mgr, &sum_dsc,
+ (struct sum **)&atc->pcm[i]);
+ if (err)
+ return err;
+
+ atc->n_pcm++;
+ }
+
+ 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};
+ int i, j;
+
+ mixer = atc->mixer;
+
+ 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]);
+ }
+
+ 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 (atc->model == CTSB1270) {
+ /* Titanium HD has a dedicated ADC for the Mic. */
+ 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[SPDIFIO], 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,
+ .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,
+ .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(sizeof(*atc), GFP_KERNEL);
+ 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..ac31b32b2
--- /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 <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/timer.h>
+#include <sound/core.h>
+
+#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;
+
+ 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 (*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);
+ 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 */
+ unsigned char n_daio;
+ unsigned char n_src;
+ unsigned char n_srcimp;
+ unsigned char n_pcm;
+
+ 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..aae544dff
--- /dev/null
+++ b/sound/pci/ctxfi/ctdaio.c
@@ -0,0 +1,755 @@
+// 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 <linux/slab.h>
+#include <linux/kernel.h>
+
+#define DAIO_OUT_MAX SPDIFOO
+
+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},
+ [SPDIFI1] = {.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},
+ [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 unsigned 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 SPDIFI1: return 1;
+ case LINEO1: return 4;
+ case LINEO2: return 7;
+ case LINEO3: return 5;
+ case LINEO4: return 6;
+ case LINEIM: return 7;
+ default: 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;
+ default: return -EINVAL;
+ }
+ default:
+ 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)
+{
+ dao->hw->dao_commit_write(dao->hw,
+ daio_device_index(dao->daio.type, dao->hw), 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((sizeof(*entry) * daio->rscl.msr), GFP_KERNEL);
+ 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((sizeof(*entry) * daio->rscr.msr), GFP_KERNEL);
+ 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_left_input(struct dao *dao)
+{
+ struct imapper *entry;
+ struct daio *daio = &dao->daio;
+ int i;
+
+ if (!dao->imappers[0])
+ return 0;
+
+ entry = dao->imappers[0];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ /* Program conjugate resources */
+ for (i = 1; i < daio->rscl.msr; i++) {
+ entry = dao->imappers[i];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ dao->imappers[i] = NULL;
+ }
+
+ kfree(dao->imappers[0]);
+ dao->imappers[0] = NULL;
+
+ return 0;
+}
+
+static int dao_clear_right_input(struct dao *dao)
+{
+ struct imapper *entry;
+ struct daio *daio = &dao->daio;
+ int i;
+
+ if (!dao->imappers[daio->rscl.msr])
+ return 0;
+
+ entry = dao->imappers[daio->rscl.msr];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ /* Program conjugate resources */
+ for (i = 1; i < daio->rscr.msr; i++) {
+ entry = dao->imappers[daio->rscl.msr + i];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ dao->imappers[daio->rscl.msr + i] = NULL;
+ }
+
+ kfree(dao->imappers[daio->rscl.msr]);
+ dao->imappers[daio->rscl.msr] = NULL;
+
+ return 0;
+}
+
+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)
+{
+ dai->hw->dai_commit_write(dai->hw,
+ daio_device_index(dai->daio.type, dai->hw), 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->type <= DAIO_OUT_MAX) {
+ 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;
+
+ 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 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;
+
+ hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(dao->daio.type, hw));
+ hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+ conf = (desc->msr & 0x7) | (desc->passthru << 3);
+ hw->daio_mgr_dao_init(mgr->mgr.ctrl_blk,
+ daio_device_index(dao->daio.type, hw), conf);
+ hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(dao->daio.type, hw));
+ 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;
+ 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 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;
+
+ 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,
+ daio_device_index(dai->daio.type, dai->hw), 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;
+ unsigned long flags;
+
+ *rdaio = NULL;
+
+ /* Check whether there are sufficient daio resources to meet request. */
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ err = daio_mgr_get_rsc(&mgr->mgr, desc->type);
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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->type <= DAIO_OUT_MAX) {
+ 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:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ daio_mgr_put_rsc(&mgr->mgr, desc->type);
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ return err;
+}
+
+static int put_daio_rsc(struct daio_mgr *mgr, struct daio *daio)
+{
+ unsigned long flags;
+
+ mgr->daio_disable(mgr, daio);
+ mgr->commit_write(mgr);
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ daio_mgr_put_rsc(&mgr->mgr, daio->type);
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+
+ if (daio->type <= DAIO_OUT_MAX) {
+ 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;
+
+ if (DAIO_OUT_MAX >= daio->type) {
+ hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ } else {
+ hw->daio_mgr_enb_dai(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ }
+ return 0;
+}
+
+static int daio_mgr_dsb_daio(struct daio_mgr *mgr, struct daio *daio)
+{
+ struct hw *hw = mgr->mgr.hw;
+
+ if (DAIO_OUT_MAX >= daio->type) {
+ hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ } else {
+ hw->daio_mgr_dsb_dai(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ }
+ 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)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ if (!entry->addr && mgr->init_imap_added) {
+ input_mapper_delete(&mgr->imappers, mgr->init_imap,
+ daio_map_op, mgr);
+ mgr->init_imap_added = 0;
+ }
+ err = input_mapper_add(&mgr->imappers, entry, daio_map_op, mgr);
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+static int daio_imap_delete(struct daio_mgr *mgr, struct imapper *entry)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ 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, struct daio_mgr **rdaio_mgr)
+{
+ int err, i;
+ struct daio_mgr *daio_mgr;
+ struct imapper *entry;
+
+ *rdaio_mgr = NULL;
+ daio_mgr = kzalloc(sizeof(*daio_mgr), GFP_KERNEL);
+ 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(sizeof(*entry), GFP_KERNEL);
+ 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(struct daio_mgr *daio_mgr)
+{
+ unsigned long flags;
+
+ /* free daio input mapper list */
+ spin_lock_irqsave(&daio_mgr->imap_lock, flags);
+ free_input_mapper_list(&daio_mgr->imappers);
+ spin_unlock_irqrestore(&daio_mgr->imap_lock, flags);
+
+ 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..431583bb0
--- /dev/null
+++ b/sound/pci/ctxfi/ctdaio.h
@@ -0,0 +1,121 @@
+/* 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 <linux/spinlock.h>
+#include <linux/list.h>
+#include <sound/core.h>
+
+/* 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 */
+ SPDIFI1, /* 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;
+};
+
+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;
+};
+
+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, struct daio_mgr **rdaio_mgr);
+int daio_mgr_destroy(struct daio_mgr *daio_mgr);
+
+#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 <linux/bug.h>
+
+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..b50d61a08
--- /dev/null
+++ b/sound/pci/ctxfi/cthardware.h
@@ -0,0 +1,214 @@
+/* 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 <linux/types.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+
+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,
+ 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 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)(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..108ab449c
--- /dev/null
+++ b/sound/pci/ctxfi/cthw20k1.c
@@ -0,0 +1,2281 @@
+// 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 <linux/types.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(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(sizeof(*blk), GFP_KERNEL);
+ 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.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(&pci->dev, DMA_BIT_MASK(dma_bits))) {
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(dma_bits));
+ } else {
+ dma_set_mask(&pci->dev, DMA_BIT_MASK(32));
+ dma_set_coherent_mask(&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 neeeded */
+ 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)
+{
+ u32 value;
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+ outl(reg, hw->io_base + 0x0);
+ value = inl(hw->io_base + 0x4);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+
+ return value;
+}
+
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+ outl(reg, hw->io_base + 0x0);
+ outl(data, hw->io_base + 0x4);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+
+}
+
+static u32 hw_read_pci(struct hw *hw, u32 reg)
+{
+ u32 value;
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+ outl(reg, hw->io_base + 0x10);
+ value = inl(hw->io_base + 0x14);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+
+ return value;
+}
+
+static void hw_write_pci(struct hw *hw, u32 reg, u32 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+ outl(reg, hw->io_base + 0x10);
+ outl(data, hw->io_base + 0x14);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+}
+
+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(sizeof(*hw20k1), GFP_KERNEL);
+ 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..b7cbe82d7
--- /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..85d1fc76f
--- /dev/null
+++ b/sound/pci/ctxfi/cthw20k2.c
@@ -0,0 +1,2350 @@
+// 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 <linux/types.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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(sizeof(*blk), GFP_KERNEL);
+ 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) {
+ /* 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(sizeof(*blk), GFP_KERNEL);
+ 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(void *blk, unsigned int idx, unsigned int conf)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* 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(sizeof(*blk), GFP_KERNEL);
+ 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 {
+ dev_alert(hw->card->dev,
+ "ERROR!!! Invalid sampling rate!!!\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (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;
+ }
+
+ /* 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) {
+ /* Titanium HD has two ADC chips, one for line in and one */
+ /* for MIC. 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 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);
+
+ /* 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;
+ }
+
+ /* Reset the ADC (reset is active low). */
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data &= ~(0x1 << 15);
+ hw_write_20kx(hw, GPIO_DATA, data);
+
+ 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);
+ }
+
+ usleep_range(10000, 11000);
+ /* Return the ADC to normal operation. */
+ data |= (0x1 << 15);
+ hw_write_20kx(hw, GPIO_DATA, data);
+ msleep(50);
+
+ /* 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;
+ 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(&pci->dev, DMA_BIT_MASK(dma_bits))) {
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(dma_bits));
+ } else {
+ dma_set_mask(&pci->dev, DMA_BIT_MASK(32));
+ dma_set_coherent_mask(&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) {
+ /* 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);
+ } else {
+ hw_write_20kx(hw, GPIO_CTRL, 0x9E5F);
+ }
+ /* 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(sizeof(*hw20k2), GFP_KERNEL);
+ 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..797b13dcd
--- /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 <linux/slab.h>
+
+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..79bc94bce
--- /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 <linux/list.h>
+
+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..6797fde3d
--- /dev/null
+++ b/sound/pci/ctxfi/ctmixer.c
@@ -0,0 +1,1225 @@
+// 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 <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+
+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);
+
+/* FIXME: this static looks like it would fail if more than one card was */
+/* installed. */
+static struct snd_kcontrol *kctls[2] = {NULL};
+
+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)
+{
+
+ if (MIXER_LINEIN_C_S == type) {
+ atc->select_line_in(atc);
+ set_switch_state(atc->mixer, MIXER_MIC_C_S, 0);
+ snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &kctls[1]->id);
+ } else if (MIXER_MIC_C_S == type) {
+ atc->select_mic_in(atc);
+ set_switch_state(atc->mixer, MIXER_LINEIN_C_S, 0);
+ snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &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)
+ 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 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:
+ kctls[0] = kctl; break;
+ case MIXER_MIC_C_S:
+ 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;
+ }
+ 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;
+ int err;
+
+ *rmixer = NULL;
+ /* Allocate mem for mixer obj */
+ mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+ if (!mixer)
+ return -ENOMEM;
+
+ mixer->amixers = kcalloc(NUM_CT_AMIXERS * CHN_NUM, sizeof(void *),
+ GFP_KERNEL);
+ if (!mixer->amixers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ mixer->sums = kcalloc(NUM_CT_SUMS * CHN_NUM, sizeof(void *),
+ GFP_KERNEL);
+ if (!mixer->sums) {
+ err = -ENOMEM;
+ goto error2;
+ }
+
+ *rmixer = mixer;
+ return 0;
+
+error2:
+ kfree(mixer->amixers);
+error1:
+ kfree(mixer);
+ return err;
+}
+
+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->sums);
+ kfree(mixer->amixers);
+ 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;
+
+ strcpy(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..770dc18a8
--- /dev/null
+++ b/sound/pci/ctxfi/ctmixer.h
@@ -0,0 +1,66 @@
+/* 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"
+
+#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;
+
+ void **amixers; /* amixer resources for volume control */
+ void **sums; /* sum resources for signal collection */
+ 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
+};
+
+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..3f48ad0e2
--- /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 <linux/slab.h>
+#include <sound/pcm.h>
+
+/* 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(sizeof(*apcm), GFP_KERNEL);
+ 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(sizeof(*apcm), GFP_KERNEL);
+ 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;
+ strlcpy(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..dfa1c62f7
--- /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..edf9d9ef9
--- /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 <linux/err.h>
+#include <linux/slab.h>
+
+#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(((amount + 8 - 1) / 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..92146054a
--- /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 <linux/types.h>
+
+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..7d2bda0c3
--- /dev/null
+++ b/sound/pci/ctxfi/ctsrc.c
@@ -0,0 +1,882 @@
+// 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 <linux/slab.h>
+
+#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;
+ unsigned long flags;
+
+ *rsrc = NULL;
+
+ /* Check whether there are sufficient src resources to meet request. */
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ if (MEMRD == desc->mode)
+ err = mgr_get_resource(&mgr->mgr, desc->multi, &idx);
+ else
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ if (MEMRD == desc->mode)
+ mgr_put_resource(&mgr->mgr, desc->multi, idx);
+ else
+ mgr_put_resource(&mgr->mgr, 1, idx);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ return err;
+}
+
+static int put_src_rsc(struct src_mgr *mgr, struct src *src)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ 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));
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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, struct src_mgr **rsrc_mgr)
+{
+ int err, i;
+ struct src_mgr *src_mgr;
+
+ *rsrc_mgr = NULL;
+ src_mgr = kzalloc(sizeof(*src_mgr), GFP_KERNEL);
+ 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(struct src_mgr *src_mgr)
+{
+ 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;
+
+ /* Reserve memory for imapper nodes */
+ srcimp->imappers = kcalloc(desc->msr, sizeof(struct imapper),
+ GFP_KERNEL);
+ if (!srcimp->imappers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+
+ /* 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;
+
+error1:
+ rsc_uninit(&srcimp->rsc);
+ return err;
+}
+
+static int srcimp_rsc_uninit(struct srcimp *srcimp)
+{
+ kfree(srcimp->imappers);
+ srcimp->imappers = NULL;
+ 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;
+ unsigned long flags;
+
+ *rsrcimp = NULL;
+
+ /* Allocate mem for SRCIMP resource */
+ srcimp = kzalloc(sizeof(*srcimp), GFP_KERNEL);
+ if (!srcimp)
+ return -ENOMEM;
+
+ /* Check whether there are sufficient SRCIMP resources. */
+ err = 0;
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < desc->msr; i++) {
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+ if (err)
+ break;
+
+ srcimp->idx[i] = idx;
+ }
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i--; i >= 0; i--)
+ mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ kfree(srcimp);
+ return err;
+}
+
+static int put_srcimp_rsc(struct srcimp_mgr *mgr, struct srcimp *srcimp)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < srcimp->rsc.msr; i++)
+ mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ 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)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ 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;
+ }
+ err = input_mapper_add(&mgr->imappers, entry, srcimp_map_op, mgr);
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+static int srcimp_imap_delete(struct srcimp_mgr *mgr, struct imapper *entry)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+int srcimp_mgr_create(struct hw *hw, struct srcimp_mgr **rsrcimp_mgr)
+{
+ int err;
+ struct srcimp_mgr *srcimp_mgr;
+ struct imapper *entry;
+
+ *rsrcimp_mgr = NULL;
+ srcimp_mgr = kzalloc(sizeof(*srcimp_mgr), GFP_KERNEL);
+ 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(sizeof(*entry), GFP_KERNEL);
+ 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(struct srcimp_mgr *srcimp_mgr)
+{
+ unsigned long flags;
+
+ /* free src input mapper list */
+ spin_lock_irqsave(&srcimp_mgr->imap_lock, flags);
+ free_input_mapper_list(&srcimp_mgr->imappers);
+ spin_unlock_irqrestore(&srcimp_mgr->imap_lock, flags);
+
+ 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..120496228
--- /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 <linux/spinlock.h>
+#include <linux/list.h>
+#include <sound/core.h>
+
+#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];
+ struct imapper *imappers;
+ unsigned int mapped; /* A bit-map indicating which conj rsc is mapped */
+ struct srcimp_mgr *mgr;
+ const struct srcimp_rsc_ops *ops;
+};
+
+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, struct src_mgr **rsrc_mgr);
+int src_mgr_destroy(struct src_mgr *src_mgr);
+/* Constructor and destructor of SRCIMP resource manager */
+int srcimp_mgr_create(struct hw *hw, struct srcimp_mgr **rsrc_mgr);
+int srcimp_mgr_destroy(struct srcimp_mgr *srcimp_mgr);
+
+#endif /* CTSRC_H */
diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c
new file mode 100644
index 000000000..0bb447ccd
--- /dev/null
+++ b/sound/pci/ctxfi/cttimer.c
@@ -0,0 +1,439 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCM timer handling on ctxfi
+ */
+
+#include <linux/slab.h>
+#include <linux/math64.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#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 = from_timer(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 long flags;
+ 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;
+ spin_lock_irqsave(&ti->lock, flags);
+ if (ti->running)
+ mod_timer(&ti->timer, jiffies + interval);
+ spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ti->lock, flags);
+ ti->running = 1;
+ mod_timer(&ti->timer,
+ jiffies + (runtime->period_size * HZ +
+ (runtime->rate - 1)) / runtime->rate);
+ spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_stop(struct ct_timer_instance *ti)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ti->lock, flags);
+ ti->running = 0;
+ del_timer(&ti->timer);
+ spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_prepare(struct ct_timer_instance *ti)
+{
+ ct_systimer_stop(ti);
+ try_to_del_timer_sync(&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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->list_lock, flags);
+ 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);
+ }
+ }
+ spin_unlock_irqrestore(&atimer->list_lock, flags);
+}
+
+/* Handle timer-interrupt */
+static void ct_xfitimer_callback(struct ct_timer *atimer)
+{
+ int update;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ 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;
+ spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ if (atimer->irq_handling) {
+ /* reached from IRQ handler; let it handle later */
+ atimer->reprogram = 1;
+ spin_unlock_irqrestore(&atimer->lock, flags);
+ return;
+ }
+
+ ct_xfitimer_irq_stop(atimer);
+ ct_xfitimer_reprogram(atimer, 0);
+ spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+static void ct_xfitimer_start(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&atimer->lock, flags);
+ ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_stop(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ list_del_init(&ti->running_list);
+ ti->running = 0;
+ spin_unlock_irqrestore(&atimer->lock, flags);
+ 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);
+
+ spin_lock_irq(&atimer->list_lock);
+ list_add(&ti->instance_list, &atimer->instance_head);
+ spin_unlock_irq(&atimer->list_lock);
+
+ 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);
+
+ spin_lock_irq(&atimer->list_lock);
+ list_del(&ti->instance_list);
+ spin_unlock_irq(&atimer->list_lock);
+
+ 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(sizeof(*atimer), GFP_KERNEL);
+ 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 <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+
+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..bde28aa9e
--- /dev/null
+++ b/sound/pci/ctxfi/ctvmem.c
@@ -0,0 +1,242 @@
+// 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 <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+
+#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 = NULL, *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;
+ }
+
+ mutex_lock(&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)
+ goto out;
+
+ if (entry->size == size) {
+ /* Move the vm node from unused list to used list directly */
+ list_move(&entry->list, &vm->used);
+ vm->size -= size;
+ block = entry;
+ goto out;
+ }
+
+ block = kzalloc(sizeof(*block), GFP_KERNEL);
+ if (!block)
+ goto out;
+
+ block->addr = entry->addr;
+ block->size = size;
+ list_add(&block->list, &vm->used);
+ entry->addr += size;
+ entry->size -= size;
+ vm->size -= size;
+
+ out:
+ mutex_unlock(&vm->lock);
+ 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);
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&vm->lock);
+}
+
+/* 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(sizeof(*vm), GFP_KERNEL);
+ 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(sizeof(*block), GFP_KERNEL);
+ 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..54818a3c2
--- /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 4 /* num of device page table pages */
+
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <sound/memalloc.h>
+
+/* 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..8c07c6463
--- /dev/null
+++ b/sound/pci/ctxfi/xfi.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * xfi linux driver.
+ *
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/pci_ids.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "ctatc.h"
+#include "cthardware.h"
+
+MODULE_AUTHOR("Creative Technology Ltd");
+MODULE_DESCRIPTION("X-Fi driver version 1.03");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs, Sound Blaster X-Fi}");
+
+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;
+
+ strcpy(card->driver, "SB-XFi");
+ strcpy(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..4865b8fe7
--- /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 <pochini@shiny.it>
+#
+
+snd-darla20-objs := darla20.o
+snd-gina20-objs := gina20.o
+snd-layla20-objs := layla20.o
+snd-darla24-objs := darla24.o
+snd-gina24-objs := gina24.o
+snd-layla24-objs := layla24.o
+snd-mona-objs := mona.o
+snd-mia-objs := mia.o
+snd-echo3g-objs := echo3g.o
+snd-indigo-objs := indigo.o
+snd-indigoio-objs := indigoio.o
+snd-indigodj-objs := indigodj.o
+snd-indigoiox-objs := indigoiox.o
+snd-indigodjx-objs := 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..e295c71c7
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x1801, 0xECC0, 0x0010, 0, 0, 0}, /* DSP 56301 Darla20 rev.0 */
+ {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..320837ba7
--- /dev/null
+++ b/sound/pci/echoaudio/darla20_dsp.c
@@ -0,0 +1,129 @@
+/***************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..ae816e78f
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x1801, 0xECC0, 0x0040, 0, 0, 0}, /* DSP 56301 Darla24 rev.0 */
+ {0x1057, 0x1801, 0xECC0, 0x0041, 0, 0, 0}, /* DSP 56301 Darla24 rev.1 */
+ {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_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..8736b5e81
--- /dev/null
+++ b/sound/pci/echoaudio/darla24_dsp.c
@@ -0,0 +1,163 @@
+/***************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..3d37bb403
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x0100, 0, 0, 0}, /* Echo 3G */
+ {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_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..6deb80c42
--- /dev/null
+++ b/sound/pci/echoaudio/echo3g_dsp.c
@@ -0,0 +1,131 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+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 <linux/interrupt.h>
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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..a20b2bb5c
--- /dev/null
+++ b/sound/pci/echoaudio/echoaudio.c
@@ -0,0 +1,2290 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALSA driver for Echoaudio soundcards.
+ * Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ * Copyright (C) 2020 Mark Hills <mark@xwax.org>
+ */
+
+#include <linux/module.h>
+
+MODULE_AUTHOR("Giuliano Pochini <pochini@shiny.it>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Echoaudio " ECHOCARD_NAME " soundcards driver");
+MODULE_SUPPORTED_DEVICE("{{Echoaudio," ECHOCARD_NAME "}}");
+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];
+
+#ifdef CONFIG_PM_SLEEP
+ 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;
+ }
+#endif
+
+ 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);
+#ifdef CONFIG_PM_SLEEP
+ else
+ chip->fw_cache[fw_index] = *fw_entry;
+#endif
+ return err;
+}
+
+
+
+static void free_firmware(const struct firmware *fw_entry,
+ struct echoaudio *chip)
+{
+#ifdef CONFIG_PM_SLEEP
+ dev_dbg(chip->card->dev, "firmware not released (kept in cache)\n");
+#else
+ release_firmware(fw_entry);
+#endif
+}
+
+
+
+static void free_firmware_cache(struct echoaudio *chip)
+{
+#ifdef CONFIG_PM_SLEEP
+ 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);
+ }
+
+#endif
+}
+
+
+
+/******************************************************************************
+ 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;
+
+ mutex_lock(&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);
+ }
+
+ mutex_unlock(&chip->mode_mutex);
+ 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(sizeof(struct audiopipe), GFP_KERNEL);
+ 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 */
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &pipe->constr)) < 0)
+ return err;
+
+ /* All periods should have the same size */
+ if ((err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) < 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 */
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ 32)) < 0)
+ return err;
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ 32)) < 0)
+ return err;
+
+ if ((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)) < 0)
+ return err;
+
+ /* Allocate a page for the scatter-gather list */
+ if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ &chip->pci->dev,
+ PAGE_SIZE, &pipe->sgpage)) < 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;
+
+ if ((err = pcm_open(substream, num_analog_busses_in(chip) -
+ substream->number)) < 0)
+ return err;
+ if ((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)) < 0)
+ return err;
+ if ((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)) < 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
+ if ((err = pcm_open(substream, max_channels - substream->number)) < 0)
+ return err;
+ if ((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)) < 0)
+ return err;
+ if ((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)) < 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;
+ mutex_lock(&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)
+ goto din_exit;
+
+ if ((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)) < 0)
+ goto din_exit;
+ if ((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)) < 0)
+ goto din_exit;
+
+din_exit:
+ mutex_unlock(&chip->mode_mutex);
+ return err;
+}
+
+
+
+#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;
+ mutex_lock(&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)
+ goto dout_exit;
+
+ if ((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)) < 0)
+ goto dout_exit;
+ if ((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)) < 0)
+ goto dout_exit;
+
+dout_exit:
+ mutex_unlock(&chip->mode_mutex);
+ return err;
+}
+
+#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
+ */
+
+ mutex_lock(&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;
+ }
+
+ mutex_unlock(&chip->mode_mutex);
+ 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
+ */
+ spin_lock_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) {
+ spin_unlock_irq(&chip->lock);
+ dev_err(chip->card->dev, "allocate_pipes(%d) err=%d\n",
+ pipe_index, err);
+ return err;
+ }
+ spin_unlock_irq(&chip->lock);
+ 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;
+ spin_lock_irq(&chip->lock);
+ set_sample_rate(chip, hw_params->rate_num / hw_params->rate_den);
+ spin_unlock_irq(&chip->lock);
+ 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;
+
+ spin_lock_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;
+ }
+ spin_unlock_irq(&chip->lock);
+
+ 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
+ */
+
+ spin_lock_irq(&chip->lock);
+
+ if (snd_BUG_ON(!is_pipe_allocated(chip, pipe_index))) {
+ spin_unlock_irq(&chip->lock);
+ return -EINVAL;
+ }
+
+ set_audio_format(chip, pipe_index, &format);
+ spin_unlock_irq(&chip->lock);
+
+ 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);
+ }
+ }
+ }
+
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->lock);
+ 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 */
+ if ((err = snd_pcm_new(chip->card, "PCM", 0, num_pipes_out(chip),
+ num_analog_busses_in(chip), &pcm)) < 0)
+ return err;
+ pcm->private_data = chip;
+ chip->analog_pcm = pcm;
+ strcpy(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 */
+ if ((err = snd_pcm_new(chip->card, "Digital PCM", 1, 0,
+ num_digital_busses_in(chip), &pcm)) < 0)
+ return err;
+ pcm->private_data = chip;
+ chip->digital_pcm = pcm;
+ strcpy(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 */
+ if ((err = snd_pcm_new(chip->card, "Analog PCM", 0,
+ num_analog_busses_out(chip),
+ num_analog_busses_in(chip), &pcm)) < 0)
+ return err;
+ pcm->private_data = chip;
+ chip->analog_pcm = pcm;
+ strcpy(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 */
+ if ((err = snd_pcm_new(chip->card, "Digital PCM", 1,
+ num_digital_busses_out(chip),
+ num_digital_busses_in(chip), &pcm)) < 0)
+ return err;
+ pcm->private_data = chip;
+ chip->digital_pcm = pcm;
+ strcpy(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);
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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);
+ spin_lock_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.
+ */
+ spin_unlock_irq(&chip->lock);
+ 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) {
+ spin_lock_irq(&chip->lock);
+ set_monitor_gain(chip, out, in, gain);
+ update_output_line_level(chip);
+ spin_unlock_irq(&chip->lock);
+ 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]) {
+ spin_lock_irq(&chip->lock);
+ set_vmixer_gain(chip, out, vch, ucontrol->value.integer.value[0]);
+ update_vmixer_level(chip);
+ spin_unlock_irq(&chip->lock);
+ 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. */
+ mutex_lock(&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 */
+ }
+ mutex_unlock(&chip->mode_mutex);
+ }
+ 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) {
+ spin_lock_irq(&chip->lock);
+ set_professional_spdif(chip, mode);
+ spin_unlock_irq(&chip->lock);
+ 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) {
+ mutex_lock(&chip->mode_mutex);
+ spin_lock_irq(&chip->lock);
+ if ((changed = set_input_clock(chip, dclock)) == 0)
+ changed = 1; /* no errors */
+ spin_unlock_irq(&chip->lock);
+ mutex_unlock(&chip->mode_mutex);
+ }
+
+ 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) {
+ spin_lock_irq(&chip->lock);
+ changed = set_phantom_power(chip, power);
+ spin_unlock_irq(&chip->lock);
+ 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) {
+ spin_lock_irq(&chip->lock);
+ changed = set_input_auto_mute(chip, automute);
+ spin_unlock_irq(&chip->lock);
+ 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);
+ spin_lock_irq(&chip->lock);
+ set_meters_on(chip, ucontrol->value.integer.value[0]);
+ spin_unlock_irq(&chip->lock);
+ 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 int snd_echo_free(struct echoaudio *chip)
+{
+ if (chip->comm_page)
+ rest_in_peace(chip);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ if (chip->comm_page)
+ snd_dma_free_pages(&chip->commpage_dma_buf);
+
+ iounmap(chip->dsp_registers);
+ release_and_free_resource(chip->iores);
+ pci_disable_device(chip->pci);
+
+ /* release chip data */
+ free_firmware_cache(chip);
+ kfree(chip);
+ return 0;
+}
+
+
+
+static int snd_echo_dev_free(struct snd_device *device)
+{
+ struct echoaudio *chip = device->device_data;
+
+ return snd_echo_free(chip);
+}
+
+
+
+/* <--snd_echo_probe() */
+static int snd_echo_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct echoaudio **rchip)
+{
+ struct echoaudio *chip;
+ int err;
+ size_t sz;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_echo_dev_free,
+ };
+
+ *rchip = NULL;
+
+ pci_write_config_byte(pci, PCI_LATENCY_TIMER, 0xC0);
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ pci_set_master(pci);
+
+ /* Allocate chip if needed */
+ if (!*rchip) {
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ dev_dbg(card->dev, "chip=%p\n", chip);
+ 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;
+ } else {
+ /* If this was called from the resume function, chip is
+ * already allocated and it contains current card settings.
+ */
+ chip = *rchip;
+ }
+
+ /* PCI resource allocation */
+ 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 */
+
+ if ((chip->iores = request_mem_region(chip->dsp_registers_phys, sz,
+ ECHOCARD_NAME)) == NULL) {
+ dev_err(chip->card->dev, "cannot get memory region\n");
+ snd_echo_free(chip);
+ return -EBUSY;
+ }
+ chip->dsp_registers = ioremap(chip->dsp_registers_phys, sz);
+ if (!chip->dsp_registers) {
+ dev_err(chip->card->dev, "ioremap failed\n");
+ snd_echo_free(chip);
+ return -ENOMEM;
+ }
+
+ if (request_irq(pci->irq, snd_echo_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(chip->card->dev, "cannot grab irq\n");
+ snd_echo_free(chip);
+ 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);
+
+ /* 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 */
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev,
+ sizeof(struct comm_page),
+ &chip->commpage_dma_buf) < 0) {
+ dev_err(chip->card->dev, "cannot allocate the comm page\n");
+ snd_echo_free(chip);
+ 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);
+ snd_echo_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_echo_free(chip);
+ return err;
+ }
+ *rchip = chip;
+ /* Init done ! */
+ 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;
+ __maybe_unused int i;
+ int err;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ i = 0;
+ err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ chip = NULL; /* Tells snd_echo_create to allocate chip */
+ if ((err = snd_echo_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ strcpy(card->driver, "Echo_" ECHOCARD_NAME);
+ strcpy(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);
+
+ if ((err = snd_echo_new_pcm(chip)) < 0) {
+ dev_err(chip->card->dev, "new pcm error %d\n", err);
+ snd_card_free(card);
+ return err;
+ }
+
+#ifdef ECHOCARD_HAS_MIDI
+ if (chip->has_midi) { /* Some Mia's do not have midi */
+ if ((err = snd_echo_midi_create(card, chip)) < 0) {
+ dev_err(chip->card->dev, "new midi error %d\n", err);
+ snd_card_free(card);
+ return err;
+ }
+ }
+#endif
+
+#ifdef ECHOCARD_HAS_VMIXER
+ snd_echo_vmixer.count = num_pipes_out(chip) * num_busses_out(chip);
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vmixer, chip))) < 0)
+ goto ctl_error;
+#ifdef ECHOCARD_HAS_LINE_OUT_GAIN
+ err = snd_ctl_add(chip->card,
+ snd_ctl_new1(&snd_echo_line_output_gain, chip));
+ if (err < 0)
+ goto ctl_error;
+#endif
+#else /* ECHOCARD_HAS_VMIXER */
+ err = snd_ctl_add(chip->card,
+ snd_ctl_new1(&snd_echo_pcm_output_gain, chip));
+ if (err < 0)
+ goto ctl_error;
+#endif /* ECHOCARD_HAS_VMIXER */
+
+#ifdef ECHOCARD_HAS_INPUT_GAIN
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_line_input_gain, chip))) < 0)
+ goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+ if (!chip->hasnt_input_nominal_level)
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_intput_nominal_level, chip))) < 0)
+ goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_output_nominal_level, chip))) < 0)
+ goto ctl_error;
+#endif
+
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vumeters_switch, chip))) < 0)
+ goto ctl_error;
+
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vumeters, chip))) < 0)
+ goto ctl_error;
+
+#ifdef ECHOCARD_HAS_MONITOR
+ snd_echo_monitor_mixer.count = num_busses_in(chip) * num_busses_out(chip);
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_monitor_mixer, chip))) < 0)
+ goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_automute_switch, chip))) < 0)
+ goto ctl_error;
+#endif
+
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_channels_info, chip))) < 0)
+ goto ctl_error;
+
+#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+ /* Creates a list of available digital modes */
+ chip->num_digital_modes = 0;
+ for (i = 0; i < 6; i++)
+ if (chip->digital_modes & (1 << i))
+ chip->digital_mode_list[chip->num_digital_modes++] = i;
+
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_digital_mode_switch, chip))) < 0)
+ goto ctl_error;
+#endif /* ECHOCARD_HAS_DIGITAL_MODE_SWITCH */
+
+#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK
+ /* Creates a list of available clock sources */
+ chip->num_clock_sources = 0;
+ for (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);
+ if ((err = snd_ctl_add(chip->card, chip->clock_src_ctl)) < 0)
+ goto ctl_error;
+ }
+#endif /* ECHOCARD_HAS_EXTERNAL_CLOCK */
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_spdif_mode_switch, chip))) < 0)
+ goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_PHANTOM_POWER
+ if (chip->has_phantom_power)
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_phantom_power_switch, chip))) < 0)
+ goto ctl_error;
+#endif
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto ctl_error;
+ dev_info(card->dev, "Card registered: %s\n", card->longname);
+
+ pci_set_drvdata(pci, chip);
+ dev++;
+ return 0;
+
+ctl_error:
+ dev_err(card->dev, "new control error %d\n", err);
+ snd_card_free(card);
+ return err;
+}
+
+
+
+#if defined(CONFIG_PM_SLEEP)
+
+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
+ spin_lock_irq(&chip->lock);
+ if (wait_handshake(chip)) {
+ spin_unlock_irq(&chip->lock);
+ return -EIO;
+ }
+ clear_handshake(chip);
+ if (send_vector(chip, DSP_VC_GO_COMATOSE) < 0) {
+ spin_unlock_irq(&chip->lock);
+ return -EIO;
+ }
+ spin_unlock_irq(&chip->lock);
+
+ 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_rettings(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 SIMPLE_DEV_PM_OPS(snd_echo_pm, snd_echo_suspend, snd_echo_resume);
+#define SND_ECHO_PM_OPS &snd_echo_pm
+#else
+#define SND_ECHO_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+
+static void snd_echo_remove(struct pci_dev *pci)
+{
+ struct echoaudio *chip;
+
+ chip = pci_get_drvdata(pci);
+ if (chip)
+ snd_card_free(chip->card);
+}
+
+
+
+/******************************************************************************
+ 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,
+ .remove = snd_echo_remove,
+ .driver = {
+ .pm = SND_ECHO_PM_OPS,
+ },
+};
+
+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..0afe13f7b
--- /dev/null
+++ b/sound/pci/echoaudio/echoaudio.h
@@ -0,0 +1,566 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ ****************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+ ****************************************************************************
+
+
+ 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<<ECHO_CLOCK_MTC)
+
+
+/***************************************************************************
+
+ Digital modes
+
+****************************************************************************/
+
+/*
+ * Digital modes for Mona, Layla24, and Gina24
+ */
+#define DIGITAL_MODE_NONE 0xFF
+#define DIGITAL_MODE_SPDIF_RCA 0
+#define DIGITAL_MODE_SPDIF_OPTICAL 1
+#define DIGITAL_MODE_ADAT 2
+#define DIGITAL_MODE_SPDIF_CDROM 3
+#define DIGITAL_MODES 4
+
+/*
+ * Digital mode capability masks
+ */
+#define ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA (1 << DIGITAL_MODE_SPDIF_RCA)
+#define ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL (1 << DIGITAL_MODE_SPDIF_OPTICAL)
+#define ECHOCAPS_HAS_DIGITAL_MODE_ADAT (1 << DIGITAL_MODE_ADAT)
+#define ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_CDROM (1 << DIGITAL_MODE_SPDIF_CDROM)
+
+
+#define EXT_3GBOX_NC 0x01 /* 3G box not connected */
+#define EXT_3GBOX_NOT_SET 0x02 /* 3G box not detected yet */
+
+
+#define ECHOGAIN_MUTED (-128) /* Minimum possible gain */
+#define ECHOGAIN_MINOUT (-128) /* Min output gain (dB) */
+#define ECHOGAIN_MAXOUT (6) /* Max output gain (dB) */
+#define ECHOGAIN_MININP (-50) /* Min input gain (0.5 dB) */
+#define ECHOGAIN_MAXINP (50) /* Max input gain (0.5 dB) */
+
+#define PIPE_STATE_STOPPED 0 /* Pipe has been reset */
+#define PIPE_STATE_PAUSED 1 /* Pipe has been stopped */
+#define PIPE_STATE_STARTED 2 /* Pipe has been started */
+#define PIPE_STATE_PENDING 3 /* Pipe has pending start */
+
+
+
+struct audiopipe {
+ volatile __le32 *dma_counter; /* Commpage register that contains
+ * the current dma position
+ * (lower 32 bits only)
+ */
+ u32 last_period; /* Counter position last time a
+ * period elapsed
+ */
+ u32 last_counter; /* Used exclusively by pcm_pointer
+ * under PCM core locks.
+ * The last position, which is used
+ * to compute...
+ */
+ u32 position; /* ...the number of bytes tranferred
+ * by the DMA engine, modulo the
+ * buffer size
+ */
+ short index; /* Index of the first channel or <0
+ * if hw is not configured yet
+ */
+ short interleave;
+ struct snd_dma_buffer sgpage; /* Room for the scatter-gather list */
+ struct snd_pcm_hardware hw;
+ struct snd_pcm_hw_constraint_list constr;
+ short sglist_head;
+ char state; /* pipe state */
+};
+
+
+struct audioformat {
+ u8 interleave; /* How the data is arranged in memory:
+ * mono = 1, stereo = 2, ...
+ */
+ u8 bits_per_sample; /* 8, 16, 24, 32 (24 bits left aligned) */
+ char mono_to_stereo; /* Only used if interleave is 1 and
+ * if this is an output pipe.
+ */
+ char data_are_bigendian; /* 1 = big endian, 0 = little endian */
+};
+
+
+struct echoaudio {
+ spinlock_t lock;
+ struct snd_pcm_substream *substream[DSP_MAXPIPES];
+ struct mutex mode_mutex;
+ u16 num_digital_modes, digital_mode_list[6];
+ u16 num_clock_sources, clock_source_list[10];
+ unsigned int opencount; /* protected by mode_mutex */
+ struct snd_kcontrol *clock_src_ctl;
+ struct snd_pcm *analog_pcm, *digital_pcm;
+ struct snd_card *card;
+ const char *card_name;
+ struct pci_dev *pci;
+ unsigned long dsp_registers_phys;
+ struct resource *iores;
+ struct snd_dma_buffer commpage_dma_buf;
+ int irq;
+#ifdef ECHOCARD_HAS_MIDI
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_substream *midi_in, *midi_out;
+#endif
+ struct timer_list timer;
+ char tinuse; /* Timer in use */
+ char midi_full; /* MIDI output buffer is full */
+ char can_set_rate; /* protected by mode_mutex */
+ char rate_set; /* protected by mode_mutex */
+
+ /* This stuff is used mainly by the lowlevel code */
+ struct comm_page *comm_page; /* Virtual address of the memory
+ * seen by DSP
+ */
+ u32 pipe_alloc_mask; /* Bitmask of allocated pipes */
+ u32 pipe_cyclic_mask; /* Bitmask of pipes with cyclic
+ * buffers
+ */
+ u32 sample_rate; /* Card sample rate in Hz */
+ u8 digital_mode; /* Current digital mode
+ * (see DIGITAL_MODE_*)
+ */
+ u8 spdif_status; /* Gina20, Darla20, Darla24 - only */
+ u8 clock_state; /* Gina20, Darla20, Darla24 - only */
+ u8 input_clock; /* Currently selected sample clock
+ * source
+ */
+ u8 output_clock; /* Layla20 only */
+ char meters_enabled; /* VU-meters status */
+ char asic_loaded; /* Set true when ASIC loaded */
+ char bad_board; /* Set true if DSP won't load */
+ char professional_spdif; /* 0 = consumer; 1 = professional */
+ char non_audio_spdif; /* 3G - only */
+ char digital_in_automute; /* Gina24, Layla24, Mona - only */
+ char has_phantom_power;
+ char hasnt_input_nominal_level; /* Gina3G */
+ char phantom_power; /* Gina3G - only */
+ char has_midi;
+ char midi_input_enabled;
+
+#ifdef ECHOCARD_ECHO3G
+ /* External module -dependent pipe and bus indexes */
+ char px_digital_out, px_analog_in, px_digital_in, px_num;
+ char bx_digital_out, bx_analog_in, bx_digital_in, bx_num;
+#endif
+
+ char nominal_level[ECHO_MAXAUDIOPIPES]; /* True == -10dBV
+ * False == +4dBu */
+ s8 input_gain[ECHO_MAXAUDIOINPUTS]; /* Input level -50..+50
+ * unit is 0.5dB */
+ s8 output_gain[ECHO_MAXAUDIOOUTPUTS]; /* Output level -128..+6 dB
+ * (-128=muted) */
+ s8 monitor_gain[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOINPUTS];
+ /* -128..+6 dB */
+ s8 vmixer_gain[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOOUTPUTS];
+ /* -128..+6 dB */
+
+ u16 digital_modes; /* Bitmask of supported modes
+ * (see ECHOCAPS_HAS_DIGITAL_MODE_*) */
+ u16 input_clock_types; /* Suppoted input clock types */
+ u16 output_clock_types; /* Suppoted output clock types -
+ * Layla20 only */
+ u16 device_id, subdevice_id;
+ u16 *dsp_code; /* Current DSP code loaded,
+ * NULL if nothing loaded */
+ short dsp_code_to_load; /* DSP code to load */
+ short asic_code; /* Current ASIC code */
+ u32 comm_page_phys; /* Physical address of the
+ * memory seen by DSP */
+ u32 __iomem *dsp_registers; /* DSP's register base */
+ u32 active_mask; /* Chs. active mask or
+ * punks out */
+#ifdef CONFIG_PM_SLEEP
+ const struct firmware *fw_cache[8]; /* Cached firmwares */
+#endif
+
+#ifdef ECHOCARD_HAS_MIDI
+ u16 mtc_state; /* State for MIDI input parsing state machine */
+ u8 midi_buffer[MIDI_IN_BUFFER_SIZE];
+#endif
+};
+
+
+static int init_dsp_comm_page(struct echoaudio *chip);
+static int init_line_levels(struct echoaudio *chip);
+static int free_pipes(struct echoaudio *chip, struct audiopipe *pipe);
+static int load_firmware(struct echoaudio *chip);
+static int wait_handshake(struct echoaudio *chip);
+static int send_vector(struct echoaudio *chip, u32 command);
+static int get_firmware(const struct firmware **fw_entry,
+ struct echoaudio *chip, const short fw_index);
+static void free_firmware(const struct firmware *fw_entry,
+ struct echoaudio *chip);
+
+#ifdef ECHOCARD_HAS_MIDI
+static int enable_midi_input(struct echoaudio *chip, char enable);
+static void snd_echo_midi_output_trigger(
+ struct snd_rawmidi_substream *substream, int up);
+static int midi_service_irq(struct echoaudio *chip);
+static int snd_echo_midi_create(struct snd_card *card,
+ struct echoaudio *chip);
+#endif
+
+
+static inline void clear_handshake(struct echoaudio *chip)
+{
+ chip->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..cc3c79387
--- /dev/null
+++ b/sound/pci/echoaudio/echoaudio_3g.c
@@ -0,0 +1,433 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+
+/* 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)) {
+ 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;
+}
+
+
+
+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;
+
+ clock = 0;
+ 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;
+ }
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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..d10d0e460
--- /dev/null
+++ b/sound/pci/echoaudio/echoaudio_dsp.c
@@ -0,0 +1,1152 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+#if PAGE_SIZE < 4096
+#error PAGE_SIZE is < 4k
+#endif
+
+static int restore_dsp_rettings(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
+ if ((i = install_resident_loader(chip)) < 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) {
+ if ((box_type = check_asic_status(chip)) >= 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;
+
+ if ((box_type = load_asic(chip)) < 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_rettings(struct echoaudio *chip)
+{
+ int i, o, err;
+
+ if ((err = check_asic_status(chip)) < 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_rettings(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..aa9129519
--- /dev/null
+++ b/sound/pci/echoaudio/echoaudio_dsp.h
@@ -0,0 +1,698 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+#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..eea6fe530
--- /dev/null
+++ b/sound/pci/echoaudio/echoaudio_gml.c
@@ -0,0 +1,203 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+/* 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;
+ }
+ }
+
+ if ((err = write_control_reg(chip, control_reg, false)))
+ 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..4f864ddc9
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x1801, 0xECC0, 0x0020, 0, 0, 0}, /* DSP 56301 Gina20 rev.0 */
+ {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..b2377573d
--- /dev/null
+++ b/sound/pci/echoaudio/gina20_dsp.c
@@ -0,0 +1,215 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..eff69e83c
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x1801, 0xECC0, 0x0050, 0, 0, 0}, /* DSP 56301 Gina24 rev.0 */
+ {0x1057, 0x1801, 0xECC0, 0x0051, 0, 0, 0}, /* DSP 56301 Gina24 rev.1 */
+ {0x1057, 0x3410, 0xECC0, 0x0050, 0, 0, 0}, /* DSP 56361 Gina24 rev.0 */
+ {0x1057, 0x3410, 0xECC0, 0x0051, 0, 0, 0}, /* DSP 56361 Gina24 rev.1 */
+ {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_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..8eff2b4f5
--- /dev/null
+++ b/sound/pci/echoaudio/gina24_dsp.c
@@ -0,0 +1,345 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+ }
+
+ if ((err = load_firmware(chip)) < 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;
+ }
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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..a9f2efc58
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x0090, 0, 0, 0}, /* Indigo */
+ {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_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..c97dc83bb
--- /dev/null
+++ b/sound/pci/echoaudio/indigo_dsp.c
@@ -0,0 +1,165 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..ceda2d704
--- /dev/null
+++ b/sound/pci/echoaudio/indigo_express_dsp.c
@@ -0,0 +1,122 @@
+/************************************************************************
+
+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
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+*************************************************************************/
+
+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..14e9769ce
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x00B0, 0, 0, 0}, /* Indigo DJ*/
+ {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_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..2428b35f4
--- /dev/null
+++ b/sound/pci/echoaudio/indigodj_dsp.c
@@ -0,0 +1,165 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..a14a7dc8c
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x00E0, 0, 0, 0}, /* Indigo DJx*/
+ {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_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..5fbd4a3a3
--- /dev/null
+++ b/sound/pci/echoaudio/indigodjx_dsp.c
@@ -0,0 +1,70 @@
+/************************************************************************
+
+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
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+*************************************************************************/
+
+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..97e024450
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x00A0, 0, 0, 0}, /* Indigo IO*/
+ {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_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..79b68ba70
--- /dev/null
+++ b/sound/pci/echoaudio/indigoio_dsp.c
@@ -0,0 +1,135 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..a017c966b
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x00D0, 0, 0, 0}, /* Indigo IOx */
+ {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_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..1ae394ea7
--- /dev/null
+++ b/sound/pci/echoaudio/indigoiox_dsp.c
@@ -0,0 +1,70 @@
+/************************************************************************
+
+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
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+*************************************************************************/
+
+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..7e38bc9c0
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x1801, 0xECC0, 0x0030, 0, 0, 0}, /* DSP 56301 Layla20 rev.0 */
+ {0x1057, 0x1801, 0xECC0, 0x0031, 0, 0, 0}, /* DSP 56301 Layla20 rev.1 */
+ {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_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..5e5b6e288
--- /dev/null
+++ b/sound/pci/echoaudio/layla20_dsp.c
@@ -0,0 +1,289 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..95c52210f
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x0060, 0, 0, 0}, /* DSP 56361 Layla24 rev.0 */
+ {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..c02bc1dcc
--- /dev/null
+++ b/sound/pci/echoaudio/layla24_dsp.c
@@ -0,0 +1,394 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 0)
+ return err;
+ chip->bad_board = false;
+
+ if ((err = init_line_levels(chip)) < 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;
+ spin_lock_irq(&chip->lock);
+ set_input_clock(chip, ECHO_CLOCK_INTERNAL);
+ spin_unlock_irq(&chip->lock);
+ }
+
+ /* switch_asic() can sleep */
+ if (switch_asic(chip, asic) < 0)
+ return -EIO;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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..a2d4b0003
--- /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 <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x3410, 0xECC0, 0x0080, 0, 0, 0}, /* DSP 56361 Mia rev.0 */
+ {0x1057, 0x3410, 0xECC0, 0x0081, 0, 0, 0}, /* DSP 56361 Mia rev.1 */
+ {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_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..8f612a09c
--- /dev/null
+++ b/sound/pci/echoaudio/mia_dsp.c
@@ -0,0 +1,225 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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..6045a115c
--- /dev/null
+++ b/sound/pci/echoaudio/midi.c
@@ -0,0 +1,326 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+/******************************************************************************
+ 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 */
+ i = 1;
+ 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) {
+ spin_lock_irq(&chip->lock);
+ enable_midi_input(chip, up);
+ spin_unlock_irq(&chip->lock);
+ 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 = from_timer(chip, t, timer);
+ unsigned long flags;
+ 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 = bytes = 0;
+ spin_lock_irqsave(&chip->lock, flags);
+ 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));
+ }
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+
+static void snd_echo_midi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct echoaudio *chip = substream->rmidi->private_data;
+
+ dev_dbg(chip->card->dev, "snd_echo_midi_output_trigger(%d)\n", up);
+ spin_lock_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;
+ spin_unlock_irq(&chip->lock);
+ del_timer_sync(&chip->timer);
+ dev_dbg(chip->card->dev, "Timer removed\n");
+ return;
+ }
+ }
+ spin_unlock_irq(&chip->lock);
+
+ 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;
+
+ if ((err = snd_rawmidi_new(card, card->shortname, 0, 1, 1,
+ &chip->rmidi)) < 0)
+ return err;
+
+ strcpy(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..1b45a2b50
--- /dev/null
+++ b/sound/pci/echoaudio/mona.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALSA driver for Echoaudio soundcards.
+ * Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ */
+
+#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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <linux/atomic.h>
+#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[] = {
+ {0x1057, 0x1801, 0xECC0, 0x0070, 0, 0, 0}, /* DSP 56301 Mona rev.0 */
+ {0x1057, 0x1801, 0xECC0, 0x0071, 0, 0, 0}, /* DSP 56301 Mona rev.1 */
+ {0x1057, 0x1801, 0xECC0, 0x0072, 0, 0, 0}, /* DSP 56301 Mona rev.2 */
+ {0x1057, 0x3410, 0xECC0, 0x0070, 0, 0, 0}, /* DSP 56361 Mona rev.0 */
+ {0x1057, 0x3410, 0xECC0, 0x0071, 0, 0, 0}, /* DSP 56361 Mona rev.1 */
+ {0x1057, 0x3410, 0xECC0, 0x0072, 0, 0, 0}, /* DSP 56361 Mona rev.2 */
+ {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_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..f77db83dd
--- /dev/null
+++ b/sound/pci/echoaudio/mona_dsp.c
@@ -0,0 +1,421 @@
+/****************************************************************************
+
+ 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.
+
+ Echo Digital Audio's generic driver library is free software;
+ you can redistribute it and/or modify it under the terms of
+ the GNU General Public License as published by the Free Software
+ Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+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;
+
+ if ((err = init_dsp_comm_page(chip))) {
+ 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;
+
+ if ((err = load_firmware(chip)) < 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;
+ }
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->lock);
+ 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..17d5527be
--- /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 <perex@perex.cz>
+#
+
+snd-emu10k1-objs := 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-objs := emu10k1_synth.o emu10k1_callback.o emu10k1_patch.o
+snd-emu10k1x-objs := 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..29b7720d7
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The driver for the EMU10K1 (SB Live!) based soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Added support for Audigy 2 Value.
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("EMU10K1");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB Live!/PCI512/E-mu APS},"
+ "{Creative Labs,SB Audigy}}");
+
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+#define ENABLE_SYNTH
+#include <sound/emu10k1_synth.h>
+#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 */
+static uint delay_pcm_irq[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+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.");
+module_param_array(delay_pcm_irq, uint, NULL, 0444);
+MODULE_PARM_DESC(delay_pcm_irq, "Delay PCM interrupt by specified number of samples (default 0).");
+/*
+ * 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), 0 }, /* EMU10K1 */
+ { PCI_VDEVICE(CREATIVE, 0x0004), 1 }, /* Audigy */
+ { PCI_VDEVICE(CREATIVE, 0x0008), 1 }, /* Audigy 2 Value SB0400 */
+ { 0, }
+};
+
+/*
+ * Audigy 2 Value notes:
+ * A_IOCFG Input (GPIO)
+ * 0x400 = Front analog jack plugged in. (Green socket)
+ * 0x1000 = Read 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
+ */
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+ if (max_buffer_size[dev] < 32)
+ max_buffer_size[dev] = 32;
+ else if (max_buffer_size[dev] > 1024)
+ max_buffer_size[dev] = 1024;
+ if ((err = snd_emu10k1_create(card, pci, extin[dev], extout[dev],
+ (long)max_buffer_size[dev] * 1024 * 1024,
+ enable_ir[dev], subsystem[dev],
+ &emu)) < 0)
+ goto error;
+ card->private_data = emu;
+ emu->delay_pcm_irq = delay_pcm_irq[dev] & 0x1f;
+ if ((err = snd_emu10k1_pcm(emu, 0)) < 0)
+ goto error;
+ if ((err = snd_emu10k1_pcm_mic(emu, 1)) < 0)
+ goto error;
+ if ((err = snd_emu10k1_pcm_efx(emu, 2)) < 0)
+ goto error;
+ /* This stores the periods table. */
+ if (emu->card_capabilities->ca0151_chip) { /* P16V */
+ err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ 1024, &emu->p16v_buffer);
+ if (err < 0)
+ goto error;
+ }
+
+ if ((err = snd_emu10k1_mixer(emu, 0, 3)) < 0)
+ goto error;
+
+ if ((err = snd_emu10k1_timer(emu, 0)) < 0)
+ goto error;
+
+ if ((err = snd_emu10k1_pcm_multi(emu, 3)) < 0)
+ goto error;
+ if (emu->card_capabilities->ca0151_chip) { /* P16V */
+ if ((err = snd_p16v_pcm(emu, 4)) < 0)
+ goto error;
+ }
+ if (emu->audigy) {
+ if ((err = snd_emu10k1_audigy_midi(emu)) < 0)
+ goto error;
+ } else {
+ if ((err = snd_emu10k1_midi(emu)) < 0)
+ goto error;
+ }
+ if ((err = snd_emu10k1_fx8010_new(emu, 0)) < 0)
+ goto error;
+#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);
+ strcpy(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
+
+ strlcpy(card->driver, emu->card_capabilities->driver,
+ sizeof(card->driver));
+ strlcpy(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);
+
+ if ((err = snd_card_register(card)) < 0)
+ goto error;
+
+ if (emu->card_capabilities->emu_model)
+ schedule_delayed_work(&emu->emu1010.firmware_work, 0);
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+ error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_card_emu10k1_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+
+#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_delayed_work_sync(&emu->emu1010.firmware_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);
+
+ if (emu->card_capabilities->emu_model)
+ schedule_delayed_work(&emu->emu1010.firmware_work, 0);
+
+ 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,
+ .remove = snd_card_emu10k1_remove,
+ .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..07471c3dc
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_callback.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * synth callback routines for Emu10k1
+ *
+ * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/export.h>
+#include "emu10k1_synth_local.h"
+#include <sound/asoundef.h>
+
+/* 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 void set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
+static void set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
+static void set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
+
+/*
+ * 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,
+};
+
+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];
+ if ((ch = vp->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)
+{
+ int dcysusv;
+ struct snd_emu10k1 *hw;
+
+ hw = vp->hw;
+ dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+ snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv);
+ dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease | DCYSUSV_CHANNELENABLE_MASK;
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv);
+}
+
+
+/*
+ * 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(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
+ 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_ptr_write(hw, IFATN, vp->ch, 0xff00);
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
+ // snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0);
+ snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff);
+ snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff);
+ 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)
+ set_fmmod(hw, 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)
+ set_fm2frq2(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_Q)
+ set_filterQ(hw, vp);
+}
+
+
+/*
+ * 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);
+ 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, &hwvoice) < 0 || hwvoice == NULL)
+ 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;
+ 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;
+
+ 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) >> 1;
+ 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);
+ }
+
+ /* channel to be silent and idle */
+ snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0x0000);
+ snd_emu10k1_ptr_write(hw, VTFT, ch, 0x0000FFFF);
+ snd_emu10k1_ptr_write(hw, CVCF, ch, 0x0000FFFF);
+ snd_emu10k1_ptr_write(hw, PTRX, ch, 0);
+ snd_emu10k1_ptr_write(hw, CPF, ch, 0);
+
+ /* set pitch offset */
+ snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
+
+ /* set envelope parameters */
+ snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay);
+ snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld);
+ snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus);
+ snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay);
+ snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld);
+ /* decay/sustain parameter for volume envelope is used
+ for triggerg the voice */
+
+ /* cutoff and volume */
+ temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol;
+ snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp);
+
+ /* modulation envelope heights */
+ snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe);
+
+ /* lfo1/2 delay */
+ snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay);
+ snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay);
+
+ /* lfo1 pitch & cutoff shift */
+ set_fmmod(hw, vp);
+ /* lfo1 volume & freq */
+ snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
+ /* lfo2 pitch & freq */
+ set_fm2frq2(hw, vp);
+
+ /* reverb and loop start (reverb 8bit, MSB) */
+ temp = vp->reg.parm.reverb;
+ temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+ LIMITMAX(temp, 255);
+ addr = vp->reg.loopstart;
+ snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr);
+
+ /* chorus & loop end (chorus 8bit, MSB) */
+ addr = vp->reg.loopend;
+ temp = vp->reg.parm.chorus;
+ temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+ LIMITMAX(temp, 255);
+ temp = (temp <<24) | addr;
+ snd_emu10k1_ptr_write(hw, DSL, ch, temp);
+
+ /* clear filter delay memory */
+ snd_emu10k1_ptr_write(hw, Z1, ch, 0);
+ snd_emu10k1_ptr_write(hw, Z2, ch, 0);
+
+ /* invalidate maps */
+ temp = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0);
+ snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
+ snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
+#if 0
+ /* cache */
+ {
+ unsigned int val, sample;
+ val = 32;
+ if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
+ sample = 0x80808080;
+ else {
+ sample = 0;
+ val *= 2;
+ }
+
+ /* cache */
+ snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16);
+ snd_emu10k1_ptr_write(hw, CDE, ch, sample);
+ snd_emu10k1_ptr_write(hw, CDF, ch, sample);
+
+ /* invalidate maps */
+ temp = ((unsigned int)hw->silent_page.addr << hw_address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0);
+ snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
+ snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
+
+ /* fill cache */
+ val -= 4;
+ val <<= 25;
+ val |= 0x1c << 16;
+ snd_emu10k1_ptr_write(hw, CCR, ch, val);
+ }
+#endif
+
+ /* Q & current address (Q 4bit value, MSB) */
+ addr = vp->reg.start;
+ temp = vp->reg.parm.filterQ;
+ temp = (temp<<28) | addr;
+ if (vp->apitch < 0xe400)
+ temp |= CCCA_INTERPROM_0;
+ else {
+ unsigned int shift = (vp->apitch - 0xe000) >> 10;
+ temp |= shift << 25;
+ }
+ if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
+ temp |= CCCA_8BITSELECT;
+ snd_emu10k1_ptr_write(hw, CCCA, ch, temp);
+
+ /* reset volume */
+ temp = (unsigned int)vp->vtarget << 16;
+ snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget);
+ snd_emu10k1_ptr_write(hw, CVCF, ch, temp | 0xff00);
+ return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(struct snd_emux_voice *vp)
+{
+ unsigned int temp, 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
+ /* set pitch target and pan (volume) */
+ temp = ptarget | (vp->apan << 8) | vp->aaux;
+ snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp);
+
+ /* pitch target */
+ snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget);
+
+ /* trigger voice */
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK);
+}
+
+#define MOD_SENSE 18
+
+/* set lfo1 modulation height and cutoff */
+static void
+set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
+{
+ unsigned short fmmod;
+ short pitch;
+ unsigned char cutoff;
+ int modulation;
+
+ pitch = (char)(vp->reg.parm.fmmod>>8);
+ cutoff = (vp->reg.parm.fmmod & 0xff);
+ modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+ pitch += (MOD_SENSE * modulation) / 1200;
+ LIMITVALUE(pitch, -128, 127);
+ fmmod = ((unsigned char)pitch<<8) | cutoff;
+ snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
+{
+ unsigned short fm2frq2;
+ short pitch;
+ unsigned char freq;
+ int modulation;
+
+ pitch = (char)(vp->reg.parm.fm2frq2>>8);
+ freq = vp->reg.parm.fm2frq2 & 0xff;
+ modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+ pitch += (MOD_SENSE * modulation) / 1200;
+ LIMITVALUE(pitch, -128, 127);
+ fm2frq2 = ((unsigned char)pitch<<8) | freq;
+ snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
+{
+ unsigned int val;
+ val = snd_emu10k1_ptr_read(hw, CCCA, vp->ch) & ~CCCA_RESONANCE;
+ val |= (vp->reg.parm.filterQ << 28);
+ snd_emu10k1_ptr_write(hw, CCCA, vp->ch, val);
+}
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
new file mode 100644
index 000000000..bd70e112f
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -0,0 +1,2152 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ * Added support for Audigy 2 Value.
+ * Added EMU 1010 support.
+ * General bug fixes and enhancements.
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/iommu.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+
+
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <linux/firmware.h>
+#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(emu, DCYSUSV, ch, 0);
+ snd_emu10k1_ptr_write(emu, IP, ch, 0);
+ snd_emu10k1_ptr_write(emu, VTFT, ch, 0xffff);
+ snd_emu10k1_ptr_write(emu, CVCF, ch, 0xffff);
+ snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
+ snd_emu10k1_ptr_write(emu, CPF, ch, 0);
+ snd_emu10k1_ptr_write(emu, CCR, ch, 0);
+
+ snd_emu10k1_ptr_write(emu, PSST, ch, 0);
+ snd_emu10k1_ptr_write(emu, DSL, ch, 0x10);
+ snd_emu10k1_ptr_write(emu, CCCA, ch, 0);
+ snd_emu10k1_ptr_write(emu, Z1, ch, 0);
+ snd_emu10k1_ptr_write(emu, Z2, ch, 0);
+ snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000);
+
+ snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0);
+ snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0);
+ snd_emu10k1_ptr_write(emu, IFATN, ch, 0xffff);
+ snd_emu10k1_ptr_write(emu, PEFE, ch, 0);
+ snd_emu10k1_ptr_write(emu, FMMOD, ch, 0);
+ snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24); /* 1 Hz */
+ snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24); /* 1 Hz */
+ snd_emu10k1_ptr_write(emu, TEMPENV, ch, 0);
+
+ /*** these are last so OFF prevents writing ***/
+ snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0);
+ snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0);
+ snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0);
+ snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0);
+ snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0);
+
+ /* Audigy extra stuffs */
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, 0x4c, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, 0x4d, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, 0x4e, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, 0x4f, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100);
+ snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x3f3f3f3f);
+ snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0);
+ }
+}
+
+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, int resume)
+{
+ unsigned int silent_page;
+ int ch;
+ u32 tmp;
+
+ /* disable audio and lock cache */
+ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
+ HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
+
+ /* reset recording buffers */
+ snd_emu10k1_ptr_write(emu, MICBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
+
+ /* disable channel interrupt */
+ outl(0, emu->port + INTE);
+ snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
+
+ 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(emu, SPCS0, 0, emu->spdif_bits[0]);
+ snd_emu10k1_ptr_write(emu, SPCS1, 0, emu->spdif_bits[1]);
+ snd_emu10k1_ptr_write(emu, SPCS2, 0, emu->spdif_bits[2]);
+
+ if (emu->card_capabilities->ca0151_chip) { /* audigy2 */
+ /* Hacks for Alice3 to work independent of haP16V driver */
+ /* Setup SRCMulti_I2S SamplingRate */
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ tmp &= 0xfffff1ff;
+ tmp |= (0x2<<9);
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+
+ /* 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);
+ }
+ 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 */
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ tmp &= 0xfffff1ff;
+ tmp |= (0x2<<9);
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+
+ /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+ outl(0x600000, emu->port + 0x20);
+ outl(0x14, emu->port + 0x24);
+
+ /* Setup SRCMulti Input Audio Enable */
+ outl(0x7b0000, emu->port + 0x20);
+ outl(0xFF000000, emu->port + 0x24);
+
+ /* Setup SPDIF Out Audio Enable */
+ /* The Audigy 2 Value has a separate SPDIF out,
+ * so no need for a mixer switch
+ */
+ outl(0x7a0000, emu->port + 0x20);
+ outl(0xFF000000, emu->port + 0x24);
+ tmp = inl(emu->port + A_IOCFG) & ~0x8; /* Clear bit 3 */
+ outl(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
+ */
+ outl(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 = inl(emu->port + A_IOCFG);
+ outl(tmp | 0x4, emu->port + A_IOCFG); /* Set bit 2 for mic input */
+ tmp = inl(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, 4); /* 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) {
+ unsigned int reg = inl(emu->port + A_IOCFG);
+ outl(reg | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+ udelay(500);
+ outl(reg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+ udelay(100);
+ outl(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 */
+ unsigned int reg = inl(emu->port + A_IOCFG);
+ outl(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) {
+ outl(inl(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. */
+ outl(inl(emu->port + A_IOCFG) | 0x0040, emu->port + A_IOCFG);
+ } else if (emu->card_capabilities->ca0108_chip) { /* audigy2 value */
+ /* Unmute Analog now. */
+ outl(inl(emu->port + A_IOCFG) | 0x0060, emu->port + A_IOCFG);
+ } else {
+ /* Disable routing from AC97 line out to Front speakers */
+ outl(inl(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
+
+ snd_emu10k1_intr_enable(emu, INTE_PCIERRORENABLE);
+}
+
+int snd_emu10k1_done(struct snd_emu10k1 *emu)
+{
+ int ch;
+
+ outl(0, emu->port + INTE);
+
+ /*
+ * Shutdown the chip
+ */
+ for (ch = 0; ch < NUM_G; ch++)
+ snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0);
+ for (ch = 0; ch < NUM_G; ch++) {
+ snd_emu10k1_ptr_write(emu, VTFT, ch, 0);
+ snd_emu10k1_ptr_write(emu, CVCF, ch, 0);
+ snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
+ snd_emu10k1_ptr_write(emu, CPF, ch, 0);
+ }
+
+ /* reset recording buffers */
+ snd_emu10k1_ptr_write(emu, MICBS, 0, 0);
+ snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXBS, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+ snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K);
+ snd_emu10k1_ptr_write(emu, TCB, 0, 0);
+ 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);
+
+ /* disable channel interrupt */
+ snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
+
+ /* disable audio and lock cache */
+ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
+ snd_emu10k1_ptr_write(emu, PTB, 0, 0);
+
+ 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;
+}
+
+static int snd_emu1010_load_firmware_entry(struct snd_emu10k1 *emu,
+ const struct firmware *fw_entry)
+{
+ int n, i;
+ int reg;
+ int value;
+ __always_unused unsigned int write_post;
+ unsigned long flags;
+
+ if (!fw_entry)
+ return -EIO;
+
+ /* The FPGA is a Xilinx Spartan IIE XC2S50E */
+ /* GPIO7 -> FPGA PGMN
+ * GPIO6 -> FPGA CCLK
+ * GPIO5 -> FPGA DIN
+ * FPGA CONFIG OFF -> FPGA PGMN
+ */
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(0x00, emu->port + A_IOCFG); /* Set PGMN low for 1uS. */
+ write_post = inl(emu->port + A_IOCFG);
+ udelay(100);
+ outl(0x80, emu->port + A_IOCFG); /* Leave bit 7 set during netlist setup. */
+ write_post = inl(emu->port + A_IOCFG);
+ udelay(100); /* Allow FPGA memory to clean */
+ for (n = 0; n < fw_entry->size; n++) {
+ value = fw_entry->data[n];
+ for (i = 0; i < 8; i++) {
+ reg = 0x80;
+ if (value & 0x1)
+ reg = reg | 0x20;
+ value = value >> 1;
+ outl(reg, emu->port + A_IOCFG);
+ write_post = inl(emu->port + A_IOCFG);
+ outl(reg | 0x40, emu->port + A_IOCFG);
+ write_post = inl(emu->port + A_IOCFG);
+ }
+ }
+ /* After programming, set GPIO bit 4 high again. */
+ outl(0x10, emu->port + A_IOCFG);
+ write_post = inl(emu->port + A_IOCFG);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+ 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;
+ }
+
+ return snd_emu1010_load_firmware_entry(emu, *fw);
+}
+
+static void emu1010_firmware_work(struct work_struct *work)
+{
+ struct snd_emu10k1 *emu;
+ u32 tmp, tmp2, reg;
+ int err;
+
+ emu = container_of(work, struct snd_emu10k1,
+ emu1010.firmware_work.work);
+ if (emu->card->shutdown)
+ return;
+#ifdef CONFIG_PM_SLEEP
+ if (emu->suspend)
+ return;
+#endif
+ snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &tmp); /* IRQ Status */
+ snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg); /* OPTIONS: Which cards are attached to the EMU */
+ if (reg & EMU_HANA_OPTION_DOCK_OFFLINE) {
+ /* Audio Dock attached */
+ /* Return to Audio Dock programming mode */
+ dev_info(emu->card->dev,
+ "emu1010: Loading Audio Dock Firmware\n");
+ snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG,
+ EMU_HANA_FPGA_CONFIG_AUDIODOCK);
+ err = snd_emu1010_load_firmware(emu, 1, &emu->dock_fw);
+ if (err < 0)
+ goto next;
+
+ snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, 0);
+ snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &tmp);
+ dev_info(emu->card->dev,
+ "emu1010: EMU_HANA+DOCK_IRQ_STATUS = 0x%x\n", tmp);
+ /* ID, should read & 0x7f = 0x55 when FPGA programmed. */
+ snd_emu1010_fpga_read(emu, EMU_HANA_ID, &tmp);
+ dev_info(emu->card->dev,
+ "emu1010: EMU_HANA+DOCK_ID = 0x%x\n", tmp);
+ if ((tmp & 0x1f) != 0x15) {
+ /* FPGA failed to be programmed */
+ dev_info(emu->card->dev,
+ "emu1010: Loading Audio Dock Firmware file failed, reg = 0x%x\n",
+ tmp);
+ goto next;
+ }
+ 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);
+ /* Sync clocking between 1010 and Dock */
+ /* Allow DLL to settle */
+ msleep(10);
+ /* Unmute all. Default is muted after a firmware load */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
+ } else if (!reg && emu->emu1010.last_reg) {
+ /* Audio Dock removed */
+ dev_info(emu->card->dev, "emu1010: Audio Dock detached\n");
+ /* Unmute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
+ }
+
+ next:
+ emu->emu1010.last_reg = reg;
+ if (!emu->card->shutdown)
+ schedule_delayed_work(&emu->emu1010.firmware_work,
+ msecs_to_jiffies(1000));
+}
+
+/*
+ * EMU-1010 - details found out from this driver, official MS Win drivers,
+ * testing the card:
+ *
+ * Audigy2 (aka Alice2):
+ * ---------------------
+ * * communication over PCI
+ * * conversion of 32-bit data coming over EMU32 links from HANA FPGA
+ * to 2 x 16-bit, using internal DSP instructions
+ * * slave mode, clock supplied by HANA
+ * * linked to HANA using:
+ * 32 x 32-bit serial EMU32 output channels
+ * 16 x EMU32 input channels
+ * (?) x I2S I/O channels (?)
+ *
+ * FPGA (aka HANA):
+ * ---------------
+ * * provides all (?) physical inputs and outputs of the card
+ * (ADC, DAC, SPDIF I/O, ADAT I/O, etc.)
+ * * provides clock signal for the card and Alice2
+ * * two crystals - for 44.1kHz and 48kHz multiples
+ * * provides internal routing of signal sources to signal destinations
+ * * inputs/outputs to Alice2 - see above
+ *
+ * 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)
+{
+ unsigned int i;
+ u32 tmp, tmp2, reg;
+ int err;
+
+ dev_info(emu->card->dev, "emu1010: Special config.\n");
+ /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+ * Lock Sound Memory Cache, Lock Tank Memory Cache,
+ * Mute all codecs.
+ */
+ outl(0x0005a00c, emu->port + HCFG);
+ /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+ * Lock Tank Memory Cache,
+ * Mute all codecs.
+ */
+ outl(0x0005a004, emu->port + HCFG);
+ /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+ * Mute all codecs.
+ */
+ outl(0x0005a000, emu->port + HCFG);
+ /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+ * Mute all codecs.
+ */
+ outl(0x0005a000, emu->port + HCFG);
+
+ /* Disable 48Volt power to Audio Dock */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, 0);
+
+ /* ID, should read & 0x7f = 0x55. (Bit 7 is the IRQ bit) */
+ snd_emu1010_fpga_read(emu, EMU_HANA_ID, &reg);
+ dev_dbg(emu->card->dev, "reg1 = 0x%x\n", reg);
+ if ((reg & 0x3f) == 0x15) {
+ /* FPGA netlist already present so clear it */
+ /* Return to programming mode */
+
+ snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, 0x02);
+ }
+ snd_emu1010_fpga_read(emu, EMU_HANA_ID, &reg);
+ dev_dbg(emu->card->dev, "reg2 = 0x%x\n", reg);
+ if ((reg & 0x3f) == 0x15) {
+ /* FPGA failed to return to programming mode */
+ dev_info(emu->card->dev,
+ "emu1010: FPGA failed to return to programming mode\n");
+ return -ENODEV;
+ }
+ dev_info(emu->card->dev, "emu1010: EMU_HANA_ID = 0x%x\n", reg);
+
+ 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, &reg);
+ 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, &reg);
+ dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg);
+ snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg);
+ dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg);
+ snd_emu1010_fpga_read(emu, EMU_HANA_OPTICAL_TYPE, &tmp);
+ /* Optical -> ADAT I/O */
+ /* 0 : SPDIF
+ * 1 : ADAT
+ */
+ emu->emu1010.optical_in = 1; /* IN_ADAT */
+ emu->emu1010.optical_out = 1; /* IN_ADAT */
+ tmp = 0;
+ tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : 0) |
+ (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
+ snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp);
+ snd_emu1010_fpga_read(emu, EMU_HANA_ADC_PADS, &tmp);
+ /* Set no attenuation on Audio Dock pads. */
+ snd_emu1010_fpga_write(emu, EMU_HANA_ADC_PADS, 0x00);
+ emu->emu1010.adc_pads = 0x00;
+ snd_emu1010_fpga_read(emu, EMU_HANA_DOCK_MISC, &tmp);
+ /* Unmute Audio dock DACs, Headphone source DAC-4. */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, 0x30);
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12);
+ snd_emu1010_fpga_read(emu, EMU_HANA_DAC_PADS, &tmp);
+ /* DAC PADs. */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, 0x0f);
+ emu->emu1010.dac_pads = 0x0f;
+ snd_emu1010_fpga_read(emu, EMU_HANA_DOCK_MISC, &tmp);
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, 0x30);
+ snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &tmp);
+ /* SPDIF Format. Set Consumer mode, 24bit, copy enable */
+ snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, 0x10);
+ /* MIDI routing */
+ snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19);
+ /* Unknown. */
+ snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c);
+ /* IRQ Enable: All on */
+ /* snd_emu1010_fpga_write(emu, 0x09, 0x0f ); */
+ /* IRQ Enable: All off */
+ snd_emu1010_fpga_write(emu, EMU_HANA_IRQ_ENABLE, 0x00);
+
+ snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg);
+ dev_info(emu->card->dev, "emu1010: Card options3 = 0x%x\n", reg);
+ /* Default WCLK set to 48kHz. */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 0x00);
+ /* Word Clock source, Internal 48kHz x1 */
+ 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); */
+ /* Audio Dock LEDs. */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12);
+
+#if 0
+ /* For 96kHz */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT2);
+#endif
+#if 0
+ /* For 192kHz */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_RIGHT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT3);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT3);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_6, EMU_SRC_HAMOA_ADC_LEFT4);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_7, EMU_SRC_HAMOA_ADC_RIGHT4);
+#endif
+#if 1
+ /* For 48kHz */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_0, EMU_SRC_DOCK_MIC_A1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_1, EMU_SRC_DOCK_MIC_B1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_4, EMU_SRC_DOCK_ADC1_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_5, EMU_SRC_DOCK_ADC1_RIGHT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_6, EMU_SRC_DOCK_ADC2_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_7, EMU_SRC_DOCK_ADC2_RIGHT1);
+ /* Pavel Hofman - setting defaults for 8 more capture channels
+ * Defaults only, users will set their own values anyways, let's
+ * just copy/paste.
+ */
+
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_8, EMU_SRC_DOCK_MIC_A1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_9, EMU_SRC_DOCK_MIC_B1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_A, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_B, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_ADC1_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_ADC1_RIGHT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_E, EMU_SRC_DOCK_ADC2_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_F, EMU_SRC_DOCK_ADC2_RIGHT1);
+#endif
+#if 0
+ /* Original */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_4, EMU_SRC_HANA_ADAT);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_5, EMU_SRC_HANA_ADAT + 1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_6, EMU_SRC_HANA_ADAT + 2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_7, EMU_SRC_HANA_ADAT + 3);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_8, EMU_SRC_HANA_ADAT + 4);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_9, EMU_SRC_HANA_ADAT + 5);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_A, EMU_SRC_HANA_ADAT + 6);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_B, EMU_SRC_HANA_ADAT + 7);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_MIC_A1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_MIC_B1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_E, EMU_SRC_HAMOA_ADC_LEFT2);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE2_EMU32_F, EMU_SRC_HAMOA_ADC_LEFT2);
+#endif
+ for (i = 0; i < 0x20; i++) {
+ /* AudioDock Elink <- Silence */
+ snd_emu1010_fpga_link_dst_src_write(emu, 0x0100 + i, EMU_SRC_SILENCE);
+ }
+ for (i = 0; i < 4; i++) {
+ /* Hana SPDIF Out <- Silence */
+ snd_emu1010_fpga_link_dst_src_write(emu, 0x0200 + i, EMU_SRC_SILENCE);
+ }
+ for (i = 0; i < 7; i++) {
+ /* Hamoa DAC <- Silence */
+ snd_emu1010_fpga_link_dst_src_write(emu, 0x0300 + i, EMU_SRC_SILENCE);
+ }
+ for (i = 0; i < 7; i++) {
+ /* Hana ADAT Out <- Silence */
+ snd_emu1010_fpga_link_dst_src_write(emu, EMU_DST_HANA_ADAT + i, EMU_SRC_SILENCE);
+ }
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE_I2S0_LEFT, EMU_SRC_DOCK_ADC1_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE_I2S0_RIGHT, EMU_SRC_DOCK_ADC1_RIGHT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE_I2S1_LEFT, EMU_SRC_DOCK_ADC2_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE_I2S1_RIGHT, EMU_SRC_DOCK_ADC2_RIGHT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE_I2S2_LEFT, EMU_SRC_DOCK_ADC3_LEFT1);
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_ALICE_I2S2_RIGHT, EMU_SRC_DOCK_ADC3_RIGHT1);
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x01); /* Unmute all */
+
+ snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &tmp);
+
+ /* AC97 1.03, Any 32Meg of 2Gig address, Auto-Mute, EMU32 Slave,
+ * Lock Sound Memory Cache, Lock Tank Memory Cache,
+ * Mute all codecs.
+ */
+ outl(0x0000a000, emu->port + HCFG);
+ /* AC97 1.03, Any 32Meg of 2Gig address, Auto-Mute, EMU32 Slave,
+ * Lock Sound Memory Cache, Lock Tank Memory Cache,
+ * Un-Mute all codecs.
+ */
+ outl(0x0000a001, emu->port + HCFG);
+
+ /* Initial boot complete. Now patches */
+
+ snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &tmp);
+ snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19); /* MIDI Route */
+ snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c); /* Unknown */
+ snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19); /* MIDI Route */
+ snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c); /* Unknown */
+ snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &tmp);
+ snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, 0x10); /* SPDIF Format spdif (or 0x11 for aes/ebu) */
+
+#if 0
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32B + 2); /* ALICE2 bus 0xa2 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32B + 3); /* ALICE2 bus 0xa3 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 2); /* ALICE2 bus 0xb2 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); /* ALICE2 bus 0xb3 */
+#endif
+ /* Default outputs */
+ if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) {
+ /* 1616(M) cardbus default outputs */
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[0] = 17;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[1] = 18;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2);
+ emu->emu1010.output_source[2] = 19;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3);
+ emu->emu1010.output_source[3] = 20;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4);
+ emu->emu1010.output_source[4] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5);
+ emu->emu1010.output_source[5] = 22;
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_MANA_DAC_LEFT, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[16] = 17;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_MANA_DAC_RIGHT, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[17] = 18;
+ } else {
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[0] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[1] = 22;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2);
+ emu->emu1010.output_source[2] = 23;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3);
+ emu->emu1010.output_source[3] = 24;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4);
+ emu->emu1010.output_source[4] = 25;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5);
+ emu->emu1010.output_source[5] = 26;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC4_LEFT1, EMU_SRC_ALICE_EMU32A + 6);
+ emu->emu1010.output_source[6] = 27;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_DAC4_RIGHT1, EMU_SRC_ALICE_EMU32A + 7);
+ emu->emu1010.output_source[7] = 28;
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_PHONES_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[8] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_PHONES_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[9] = 22;
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[10] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_DOCK_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[11] = 22;
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[12] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[13] = 22;
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[14] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[15] = 22;
+ /* ALICE2 bus 0xa0 */
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT, EMU_SRC_ALICE_EMU32A + 0);
+ emu->emu1010.output_source[16] = 21;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 1, EMU_SRC_ALICE_EMU32A + 1);
+ emu->emu1010.output_source[17] = 22;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 2, EMU_SRC_ALICE_EMU32A + 2);
+ emu->emu1010.output_source[18] = 23;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 3, EMU_SRC_ALICE_EMU32A + 3);
+ emu->emu1010.output_source[19] = 24;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 4, EMU_SRC_ALICE_EMU32A + 4);
+ emu->emu1010.output_source[20] = 25;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 5, EMU_SRC_ALICE_EMU32A + 5);
+ emu->emu1010.output_source[21] = 26;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 6, EMU_SRC_ALICE_EMU32A + 6);
+ emu->emu1010.output_source[22] = 27;
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ EMU_DST_HANA_ADAT + 7, EMU_SRC_ALICE_EMU32A + 7);
+ emu->emu1010.output_source[23] = 28;
+ }
+ /* TEMP: Select SPDIF in/out */
+ /* snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, 0x0); */ /* Output spdif */
+
+ /* TEMP: Select 48kHz SPDIF out */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x0); /* Mute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 0x0); /* Default fallback clock 48kHz */
+ /* Word Clock source, Internal 48kHz x1 */
+ 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); */
+ emu->emu1010.internal_clock = 1; /* 48000 */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12); /* Set LEDs on Audio Dock */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x1); /* Unmute all */
+ /* snd_emu1010_fpga_write(emu, 0x7, 0x0); */ /* Mute all */
+ /* snd_emu1010_fpga_write(emu, 0x7, 0x1); */ /* Unmute all */
+ /* snd_emu1010_fpga_write(emu, 0xe, 0x12); */ /* Set LEDs on Audio Dock */
+
+ 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 int snd_emu10k1_free(struct snd_emu10k1 *emu)
+{
+ 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(emu, EMU_HANA_DOCK_PWR, 0);
+ }
+ cancel_delayed_work_sync(&emu->emu1010.firmware_work);
+ release_firmware(emu->firmware);
+ release_firmware(emu->dock_fw);
+ if (emu->irq >= 0)
+ free_irq(emu->irq, emu);
+ 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
+ if (emu->port)
+ pci_release_regions(emu->pci);
+ if (emu->card_capabilities->ca0151_chip) /* P16V */
+ snd_p16v_free(emu);
+ pci_disable_device(emu->pci);
+ kfree(emu);
+ return 0;
+}
+
+static int snd_emu10k1_dev_free(struct snd_device *device)
+{
+ struct snd_emu10k1 *emu = device->device_data;
+ return snd_emu10k1_free(emu);
+}
+
+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
+ */
+ {.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} ,
+ /* Tested by James@superbug.co.uk 4th Nov 2007. */
+ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x42011102,
+ .driver = "Audigy2", .name = "E-mu 1010 Notebook [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, 0202 is MAEM 8980 */
+ {.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 <mcdebugger@duganet.ru> 17th Oct 2012. */
+ /* This is MAEM8986, 0202 is MAEM8980 */
+ {.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, 0202 is MAEM8820 */
+ {.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 */
+ /* EMU0404b */
+ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40021102,
+ .driver = "Audigy2", .name = "E-mu 0404b PCI [MAEM8852]",
+ .id = "EMU0404",
+ .emu10k2_chip = 1,
+ .ca0108_chip = 1,
+ .spk71 = 1,
+ .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 new revision */
+ /* 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,
+ .spk71 = 1,
+ .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 */
+ /* EMU0404 PCIe */
+ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40051102,
+ .driver = "Audigy2", .name = "E-mu 0404 PCIe [MAEM8984]",
+ .id = "EMU0404",
+ .emu10k2_chip = 1,
+ .ca0108_chip = 1,
+ .spk71 = 1,
+ .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 PCIe ver_03 */
+ /* Note that all E-mu cards require kernel 2.6 or newer. */
+ {.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} ,
+ {.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} ,
+ {.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} ,
+ {.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;
+
+ if (!iommu_present(emu->card->dev->bus))
+ return;
+
+ 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 **remu)
+{
+ struct snd_emu10k1 *emu;
+ int idx, err;
+ int is_audigy;
+ size_t page_table_size;
+ __le32 *pgtbl;
+ unsigned int silent_page;
+ const struct snd_emu_chip_details *c;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_emu10k1_dev_free,
+ };
+
+ *remu = NULL;
+
+ /* enable PCI device */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ emu = kzalloc(sizeof(*emu), GFP_KERNEL);
+ if (emu == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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_DELAYED_WORK(&emu->emu1010.firmware_work, emu1010_firmware_work);
+ /* 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");
+ kfree(emu);
+ pci_disable_device(pci);
+ 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)
+ strlcpy(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);
+ kfree(emu);
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+ if (is_audigy)
+ emu->gpr_base = A_FXGPREGBASE;
+ else
+ emu->gpr_base = FXGPREGBASE;
+
+ err = pci_request_regions(pci, "EMU10K1");
+ if (err < 0) {
+ kfree(emu);
+ pci_disable_device(pci);
+ 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) {
+ err = -ENOMEM;
+ goto error;
+ }
+ 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 == NULL || emu->page_addr_table == NULL) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ if (snd_emu10k1_alloc_pages_maybe_wider(emu, EMUPAGESIZE,
+ &emu->silent_page) < 0) {
+ err = -ENOMEM;
+ goto error;
+ }
+ 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 == NULL) {
+ err = -ENOMEM;
+ goto error;
+ }
+ emu->memhdr->block_extra_size = sizeof(struct snd_emu10k1_memblk) -
+ sizeof(struct snd_util_memblk);
+
+ pci_set_master(pci);
+
+ emu->fx8010.fxbus_mask = 0x303f;
+ if (extin_mask == 0)
+ extin_mask = 0x3fcf;
+ if (extout_mask == 0)
+ extout_mask = 0x7fff;
+ 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)
+ goto error;
+ }
+ if (emu->card_capabilities->ecard) {
+ err = snd_emu10k1_ecard_init(emu);
+ if (err < 0)
+ goto error;
+ } else if (emu->card_capabilities->emu_model) {
+ err = snd_emu10k1_emu1010_init(emu);
+ if (err < 0) {
+ snd_emu10k1_free(emu);
+ 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 (request_irq(pci->irq, snd_emu10k1_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, emu)) {
+ err = -EBUSY;
+ goto error;
+ }
+ 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].emu = emu;
+ emu->voices[idx].number = idx;
+ }
+
+ err = snd_emu10k1_init(emu, enable_ir, 0);
+ if (err < 0)
+ goto error;
+#ifdef CONFIG_PM_SLEEP
+ err = alloc_pm_buffer(emu);
+ if (err < 0)
+ goto error;
+#endif
+
+ /* Initialize the effect engine */
+ err = snd_emu10k1_init_efx(emu);
+ if (err < 0)
+ goto error;
+ snd_emu10k1_audio_enable(emu);
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, emu, &ops);
+ if (err < 0)
+ goto error;
+
+#ifdef CONFIG_SND_PROC_FS
+ snd_emu10k1_proc_init(emu);
+#endif
+
+ *remu = emu;
+ return 0;
+
+ error:
+ snd_emu10k1_free(emu);
+ return err;
+}
+
+#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_SAMPLE_RATE,
+ 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 = inl(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, 1);
+}
+
+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)
+ outl(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..89890f245
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_patch.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Patch transfer callback for Emu10k1
+ *
+ * Copyright (C) 2000 Takashi iwai <tiwai@suse.de>
+ */
+/*
+ * 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 32
+
+/*
+ * 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)
+{
+ int offset;
+ int truesize, size, blocksize;
+ __maybe_unused int loopsize;
+ int loopend, sampleend;
+ unsigned int start_addr;
+ struct snd_emu10k1 *emu;
+
+ emu = rec->hw;
+ if (snd_BUG_ON(!sp || !hdr))
+ return -EINVAL;
+
+ if (sp->v.size == 0) {
+ dev_dbg(emu->card->dev,
+ "emu: rom font for sample %d\n", sp->v.sample);
+ return 0;
+ }
+
+ /* recalculate address offset */
+ sp->v.end -= sp->v.start;
+ sp->v.loopstart -= sp->v.start;
+ sp->v.loopend -= sp->v.start;
+ sp->v.start = 0;
+
+ /* some samples have invalid data. the addresses are corrected in voice info */
+ sampleend = sp->v.end;
+ if (sampleend > sp->v.size)
+ sampleend = sp->v.size;
+ loopend = sp->v.loopend;
+ if (loopend > sampleend)
+ loopend = sampleend;
+
+ /* be sure loop points start < end */
+ if (sp->v.loopstart >= sp->v.loopend)
+ swap(sp->v.loopstart, sp->v.loopend);
+
+ /* compute true data size to be loaded */
+ truesize = sp->v.size + BLANK_HEAD_SIZE;
+ loopsize = 0;
+#if 0 /* not supported */
+ if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+ loopsize = sp->v.loopend - sp->v.loopstart;
+ truesize += loopsize;
+#endif
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+ truesize += BLANK_LOOP_SIZE;
+
+ /* try to allocate a memory block */
+ blocksize = truesize;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ blocksize *= 2;
+ 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;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ size *= 2;
+ if (offset + size > blocksize)
+ return -EINVAL;
+ snd_emu10k1_synth_bzero(emu, sp->block, offset, size);
+ offset += size;
+
+ /* copy start->loopend */
+ size = loopend;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ size *= 2;
+ if (offset + size > blocksize)
+ return -EINVAL;
+ if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
+ snd_emu10k1_synth_free(emu, sp->block);
+ sp->block = NULL;
+ return -EFAULT;
+ }
+ offset += size;
+ data += size;
+
+#if 0 /* not supported yet */
+ /* handle reverse (or bidirectional) loop */
+ if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) {
+ /* copy loop in reverse */
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
+ int woffset;
+ unsigned short *wblock = (unsigned short*)block;
+ woffset = offset / 2;
+ if (offset + loopsize * 2 > blocksize)
+ return -EINVAL;
+ for (i = 0; i < loopsize; i++)
+ wblock[woffset + i] = wblock[woffset - i -1];
+ offset += loopsize * 2;
+ } else {
+ if (offset + loopsize > blocksize)
+ return -EINVAL;
+ for (i = 0; i < loopsize; i++)
+ block[offset + i] = block[offset - i -1];
+ offset += loopsize;
+ }
+
+ /* modify loop pointers */
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+ sp->v.loopend += loopsize;
+ } else {
+ sp->v.loopstart += loopsize;
+ sp->v.loopend += loopsize;
+ }
+ /* add sample pointer */
+ sp->v.end += loopsize;
+ }
+#endif
+
+ /* loopend -> sample end */
+ size = sp->v.size - loopend;
+ if (size < 0)
+ return -EINVAL;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ size *= 2;
+ if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
+ snd_emu10k1_synth_free(emu, sp->block);
+ sp->block = NULL;
+ return -EFAULT;
+ }
+ offset += size;
+
+ /* clear rest of samples (if any) */
+ if (offset < blocksize)
+ snd_emu10k1_synth_bzero(emu, sp->block, offset, blocksize - offset);
+
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+ /* 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;
+ }
+ }
+
+#if 0 /* not supported yet */
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) {
+ /* unsigned -> signed */
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
+ unsigned short *wblock = (unsigned short*)block;
+ for (i = 0; i < truesize; i++)
+ wblock[i] ^= 0x8000;
+ } else {
+ for (i = 0; i < truesize; i++)
+ block[i] ^= 0x80;
+ }
+ }
+#endif
+
+ /* recalculate offset */
+ start_addr = BLANK_HEAD_SIZE * 2;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ start_addr >>= 1;
+ sp->v.start += start_addr;
+ sp->v.end += start_addr;
+ sp->v.loopstart += start_addr;
+ sp->v.loopend += start_addr;
+
+ return 0;
+}
+
+/*
+ * 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..549013a4a
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_synth.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * Routines for control of EMU10K1 WaveTable synth
+ */
+
+#include "emu10k1_synth_local.h"
+#include <linux/init.h>
+#include <linux/module.h>
+
+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 device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ struct snd_emux *emux;
+ struct snd_emu10k1 *hw;
+ struct snd_emu10k1_synth_arg *arg;
+ unsigned long flags;
+
+ 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->pitch_shift = -501;
+ 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;
+ }
+
+ spin_lock_irqsave(&hw->voice_lock, flags);
+ hw->synth = emux;
+ hw->get_synth_voice = snd_emu10k1_synth_get_voice;
+ spin_unlock_irqrestore(&hw->voice_lock, flags);
+
+ dev->driver_data = emux;
+
+ return 0;
+}
+
+static int snd_emu10k1_synth_remove(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ struct snd_emux *emux;
+ struct snd_emu10k1 *hw;
+ unsigned long flags;
+
+ if (dev->driver_data == NULL)
+ return 0; /* not registered actually */
+
+ emux = dev->driver_data;
+
+ hw = emux->hw;
+ spin_lock_irqsave(&hw->voice_lock, flags);
+ hw->synth = NULL;
+ hw->get_synth_voice = NULL;
+ spin_unlock_irqrestore(&hw->voice_lock, flags);
+
+ snd_emux_free(emux);
+ return 0;
+}
+
+/*
+ * INIT part
+ */
+
+static struct snd_seq_driver emu10k1_synth_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe = snd_emu10k1_synth_probe,
+ .remove = snd_emu10k1_synth_remove,
+ },
+ .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 <tiwai@suse.de>
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1_synth.h>
+
+/* 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..def8161cd
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1x.c
@@ -0,0 +1,1617 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ * Driver EMU10K1X chips
+ *
+ * Parts of this code were adapted from audigyls.c driver which is
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ *
+ * Chips (SB0200 model):
+ * - EMU10K1X-DBQ
+ * - STAC 9708T
+ */
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+
+MODULE_AUTHOR("Francisco Moraes <fmoraes@nc.rr.com>");
+MODULE_DESCRIPTION("EMU10K1X");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Dell Creative Labs,SB Live!}");
+
+// 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 = <val> + 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;
+ struct resource *res_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 long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_emu10k1x_ptr_write(struct emu10k1x *emu,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_intr_enable(struct emu10k1x *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int intr_enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ intr_enable = inl(emu->port + INTE) | intrenb;
+ outl(intr_enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_intr_disable(struct emu10k1x *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int intr_enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ intr_enable = inl(emu->port + INTE) & ~intrenb;
+ outl(intr_enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_gpio_write(struct emu10k1x *emu, unsigned int value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(value, emu->port + GPIO);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+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;
+
+ if ((epcm = voice->epcm) == NULL)
+ 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;
+
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
+ return err;
+ }
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+ return err;
+
+ epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+ 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<<channel));
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ epcm->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<<channel));
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ return result;
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t
+snd_emu10k1x_pcm_pointer(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 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;
+
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+ return err;
+
+ epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+ 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;
+ unsigned long flags;
+ unsigned short val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ val = inw(emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_emu10k1x_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg, unsigned short val)
+{
+ struct emu10k1x *emu = ac97->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ outw(val, emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+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,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 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 int snd_emu10k1x_free(struct emu10k1x *chip)
+{
+ snd_emu10k1x_ptr_write(chip, TRIGGER_CHANNEL, 0, 0);
+ // disable interrupts
+ outl(0, chip->port + INTE);
+ // disable audio
+ outl(HCFG_LOCKSOUNDCACHE, chip->port + HCFG);
+
+ /* release the irq */
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ // release the i/o port
+ release_and_free_resource(chip->res_port);
+
+ // release the DMA
+ if (chip->dma_buffer.area) {
+ snd_dma_free_pages(&chip->dma_buffer);
+ }
+
+ pci_disable_device(chip->pci);
+
+ // release the data
+ kfree(chip);
+ return 0;
+}
+
+static int snd_emu10k1x_dev_free(struct snd_device *device)
+{
+ struct emu10k1x *chip = device->device_data;
+ return snd_emu10k1x_free(chip);
+}
+
+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;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1x", device, 1, capture, &pcm)) < 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:
+ strcpy(pcm->name, "EMU10K1X Front");
+ map = snd_pcm_std_chmaps;
+ break;
+ case 1:
+ strcpy(pcm->name, "EMU10K1X Rear");
+ map = surround_map;
+ break;
+ case 2:
+ strcpy(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 **rchip)
+{
+ struct emu10k1x *chip;
+ int err;
+ int ch;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_emu10k1x_dev_free,
+ };
+
+ *rchip = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
+ pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
+ dev_err(card->dev, "error to set 28bit mask DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ spin_lock_init(&chip->emu_lock);
+ spin_lock_init(&chip->voice_lock);
+
+ chip->port = pci_resource_start(pci, 0);
+ if ((chip->res_port = request_region(chip->port, 8,
+ "EMU10K1X")) == NULL) {
+ dev_err(card->dev, "cannot allocate the port 0x%lx\n",
+ chip->port);
+ snd_emu10k1x_free(chip);
+ return -EBUSY;
+ }
+
+ if (request_irq(pci->irq, snd_emu10k1x_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "cannot grab irq %d\n", pci->irq);
+ snd_emu10k1x_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ 4 * 1024, &chip->dma_buffer) < 0) {
+ snd_emu10k1x_free(chip);
+ 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);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+ chip, &ops)) < 0) {
+ snd_emu10k1x_free(chip);
+ return err;
+ }
+ *rchip = chip;
+ 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;
+ unsigned long flags;
+ int i;
+
+ snd_iprintf(buffer, "Registers:\n\n");
+ for(i = 0; i < 0x20; i+=4) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inl(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ 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", &reg, &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;
+
+ if ((kctl = snd_ctl_new1(&snd_emu10k1x_spdif_mask_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_emu10k1x_shared_spdif, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_emu10k1x_spdif_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ 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;
+ }
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&midi->input_lock);
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&midi->output_lock);
+}
+
+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)
+{
+ unsigned long flags;
+ int timeout, ok;
+
+ spin_lock_irqsave(&midi->input_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&midi->input_lock, flags);
+ 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;
+ unsigned long flags;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1X_MIDI_MODE_INPUT;
+ midi->substream_input = substream;
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1))
+ goto error_out;
+ if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+ goto error_out;
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+
+error_out:
+ return -EIO;
+}
+
+static int snd_emu10k1x_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ struct emu10k1x *emu;
+ struct emu10k1x_midi *midi = substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1X_MIDI_MODE_OUTPUT;
+ midi->substream_output = substream;
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1))
+ goto error_out;
+ if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+ goto error_out;
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+
+error_out:
+ return -EIO;
+}
+
+static int snd_emu10k1x_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ struct emu10k1x *emu;
+ struct emu10k1x_midi *midi = substream->rmidi->private_data;
+ unsigned long flags;
+ int err = 0;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ 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)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ err = snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return err;
+}
+
+static int snd_emu10k1x_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct emu10k1x *emu;
+ struct emu10k1x_midi *midi = substream->rmidi->private_data;
+ unsigned long flags;
+ int err = 0;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ 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)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ err = snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return err;
+}
+
+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;
+ unsigned long flags;
+
+ 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 */
+ spin_lock_irqsave(&midi->output_lock, flags);
+ 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 */
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ return;
+ }
+ mpu401_write_data(emu, midi, byte);
+ max--;
+ } else {
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ 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;
+
+ if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
+ return err;
+ midi->emu = emu;
+ spin_lock_init(&midi->open_lock);
+ spin_lock_init(&midi->input_lock);
+ spin_lock_init(&midi->output_lock);
+ strcpy(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;
+
+ if ((err = emu10k1x_midi_init(emu, midi, 0, "EMU10K1X MPU-401 (UART)")) < 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if ((err = snd_emu10k1x_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_pcm(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1x_pcm(chip, 1)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1x_pcm(chip, 2)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_ac97(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_mixer(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_midi(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_emu10k1x_proc_init(chip);
+
+ strcpy(card->driver, "EMU10K1X");
+ strcpy(card->shortname, "Dell Sound Blaster Live!");
+ sprintf(card->longname, "%s at 0x%lx irq %i",
+ card->shortname, chip->port, chip->irq);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_emu10k1x_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+// PCI IDs
+static const struct pci_device_id snd_emu10k1x_ids[] = {
+ { PCI_VDEVICE(CREATIVE, 0x0006), 0 }, /* Dell OEM version (EMU10K1) */
+ { 0, }
+};
+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,
+ .remove = snd_emu10k1x_remove,
+};
+
+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..e17b93b25
--- /dev/null
+++ b/sound/pci/emu10k1/emufx.c
@@ -0,0 +1,2710 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Routines for effect processor FX8010
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ * Added EMU 1010 support.
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/pci.h>
+#include <linux/capability.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/moduleparam.h>
+#include <linux/nospec.h>
+
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/emu10k1.h>
+
+#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
+ */
+
+static const char * const fxbuses[16] = {
+ /* 0x00 */ "PCM Left",
+ /* 0x01 */ "PCM Right",
+ /* 0x02 */ "PCM Surround Left",
+ /* 0x03 */ "PCM Surround Right",
+ /* 0x04 */ "MIDI Left",
+ /* 0x05 */ "MIDI Right",
+ /* 0x06 */ "Center",
+ /* 0x07 */ "LFE",
+ /* 0x08 */ NULL,
+ /* 0x09 */ NULL,
+ /* 0x0a */ NULL,
+ /* 0x0b */ NULL,
+ /* 0x0c */ "MIDI Reverb",
+ /* 0x0d */ "MIDI Chorus",
+ /* 0x0e */ NULL,
+ /* 0x0f */ NULL
+};
+
+static const char * const creative_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
+};
+
+static const char * const 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
+};
+
+static const char * const creative_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,
+ /* 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,
+};
+
+static const char * const 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,
+};
+
+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 *emu = snd_kcontrol_chip(kcontrol);
+ struct snd_emu10k1_fx8010_ctl *ctl =
+ (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value;
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (i = 0; i < ctl->vcount; i++)
+ ucontrol->value.integer.value[i] = ctl->value[i];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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;
+ unsigned long flags;
+ unsigned int nval, val;
+ unsigned int i, j;
+ int change = 0;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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_TABLE100:
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, 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:
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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)
+{
+ unsigned long flags;
+
+ irq->handler = handler;
+ irq->gpr_running = gpr_running;
+ irq->private_data = private_data;
+ irq->next = NULL;
+ spin_lock_irqsave(&emu->fx8010.irq_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags);
+ 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->fx8010.irq_lock, flags);
+ if ((tmp = emu->fx8010.irq_handlers) == 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;
+ }
+ spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags);
+ 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 &&
+ !strcmp(kcontrol->id.name, id->name) &&
+ kcontrol->id.index == id->index)
+ 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(sizeof(*gctl), GFP_KERNEL);
+ 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;
+ down_read(&emu->card->controls_rwsem);
+ if (snd_ctl_find_id(emu->card, gctl_id)) {
+ up_read(&emu->card->controls_rwsem);
+ err = -EEXIST;
+ goto __error;
+ }
+ up_read(&emu->card->controls_rwsem);
+ if (gctl_id->iface != SNDRV_CTL_ELEM_IFACE_MIXER &&
+ gctl_id->iface != SNDRV_CTL_ELEM_IFACE_PCM) {
+ 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(sizeof(*val), GFP_KERNEL);
+ gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+ nctl = kmalloc(sizeof(*nctl), GFP_KERNEL);
+ 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(sizeof(*ctl), GFP_KERNEL);
+ if (ctl == NULL) {
+ err = -ENOMEM;
+ kfree(knew.tlv.p);
+ goto __error;
+ }
+ knew.private_value = (unsigned long)ctl;
+ *ctl = *nctl;
+ if ((err = snd_ctl_add(emu->card, kctl = snd_ctl_new1(&knew, emu))) < 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;
+ down_write(&card->controls_rwsem);
+ ctl = snd_emu10k1_look_for_ctl(emu, &id);
+ if (ctl)
+ snd_ctl_remove(card, ctl->kcontrol);
+ up_write(&card->controls_rwsem);
+ }
+ 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(sizeof(*gctl), GFP_KERNEL);
+ 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 = (__force int)id->iface;
+ strlcpy(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 = 0;
+
+ mutex_lock(&emu->fx8010.lock);
+ err = snd_emu10k1_verify_controls(emu, icode, in_kernel);
+ if (err < 0)
+ goto __error;
+ strlcpy(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)
+ goto __error;
+ err = snd_emu10k1_gpr_poke(emu, icode, in_kernel);
+ if (err < 0)
+ goto __error;
+ err = snd_emu10k1_tram_poke(emu, icode, in_kernel);
+ if (err < 0)
+ goto __error;
+ err = snd_emu10k1_code_poke(emu, icode, in_kernel);
+ if (err < 0)
+ goto __error;
+ err = snd_emu10k1_add_controls(emu, icode, in_kernel);
+ if (err < 0)
+ goto __error;
+ /* 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);
+ __error:
+ mutex_unlock(&emu->fx8010.lock);
+ return err;
+}
+
+static int snd_emu10k1_icode_peek(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_fx8010_code *icode)
+{
+ int err;
+
+ mutex_lock(&emu->fx8010.lock);
+ strlcpy(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);
+ mutex_unlock(&emu->fx8010.lock);
+ return err;
+}
+
+static int snd_emu10k1_ipcm_poke(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_fx8010_pcm_rec *ipcm)
+{
+ unsigned int i;
+ int err = 0;
+ 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];
+ mutex_lock(&emu->fx8010.lock);
+ spin_lock_irq(&emu->reg_lock);
+ if (pcm->opened) {
+ err = -EBUSY;
+ goto __error;
+ }
+ 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) {
+ err = -EINVAL;
+ goto __error;
+ }
+ 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];
+ }
+ __error:
+ spin_unlock_irq(&emu->reg_lock);
+ mutex_unlock(&emu->fx8010.lock);
+ return err;
+}
+
+static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_fx8010_pcm_rec *ipcm)
+{
+ unsigned int i;
+ int err = 0;
+ 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];
+ mutex_lock(&emu->fx8010.lock);
+ spin_lock_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;
+ spin_unlock_irq(&emu->reg_lock);
+ mutex_unlock(&emu->fx8010.lock);
+ return err;
+}
+
+#define SND_EMU10K1_GPR_CONTROLS 44
+#define SND_EMU10K1_INPUTS 12
+#define SND_EMU10K1_PLAYBACK_CHANNELS 8
+#define SND_EMU10K1_CAPTURE_CHANNELS 4
+
+static void
+snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl,
+ const char *name, int gpr, int defval)
+{
+ ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, name);
+ ctl->vcount = ctl->count = 1;
+ ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+ if (high_res_gpr_volume) {
+ ctl->min = 0;
+ ctl->max = 0x7fffffff;
+ ctl->tlv = snd_emu10k1_db_linear;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_NONE;
+ } else {
+ ctl->min = 0;
+ ctl->max = 100;
+ ctl->tlv = snd_emu10k1_db_scale1;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100;
+ }
+}
+
+static void
+snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl,
+ const char *name, int gpr, int defval)
+{
+ ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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;
+ if (high_res_gpr_volume) {
+ ctl->min = 0;
+ ctl->max = 0x7fffffff;
+ ctl->tlv = snd_emu10k1_db_linear;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_NONE;
+ } else {
+ ctl->min = 0;
+ ctl->max = 100;
+ ctl->tlv = snd_emu10k1_db_scale1;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100;
+ }
+}
+
+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 = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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 = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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 HANA
+ * to 2 x 16-bit registers in audigy - their values are read via DMA.
+ * Conversion is performed by Audigy DSP instructions of FX8010.
+ */
+static int 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)
+{
+ A_OP(icode, ptr, iACC3, A_GPR(tmp + 1), reg_in, A_C_00000000, A_C_00000000);
+ A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp + 1), A_GPR(bit_shifter16 - 1), A_C_00000000);
+ A_OP(icode, ptr, iTSTNEG, A_GPR(tmp + 2), A_GPR(tmp), A_C_80000000, A_GPR(bit_shifter16 - 2));
+ A_OP(icode, ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_C_80000000, A_C_00000000);
+ A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp), A_GPR(bit_shifter16 - 3), A_C_00000000);
+ A_OP(icode, ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A_GPR(tmp), A_C_00010000);
+ A_OP(icode, ptr, iANDXOR, reg_out, A_GPR(tmp), A_C_ffffffff, A_GPR(tmp + 2));
+ A_OP(icode, ptr, iACC3, reg_out + 1, A_GPR(tmp + 1), A_C_00000000, A_C_00000000);
+ return 1;
+}
+
+/*
+ * initial DSP configuration for Audigy
+ */
+
+static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu)
+{
+ int err, i, z, gpr, nctl;
+ int bit_shifter16;
+ const int playback = 10;
+ const int capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); /* we reserve 10 voices */
+ const int stereo_mix = capture + 2;
+ const int tmp = 0x88;
+ u32 ptr;
+ struct snd_emu10k1_fx8010_code *icode = NULL;
+ struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl;
+ u32 *gpr_map;
+
+ err = -ENOMEM;
+ icode = kzalloc(sizeof(*icode), GFP_KERNEL);
+ if (!icode)
+ return err;
+
+ icode->gpr_map = kcalloc(512 + 256 + 256 + 2 * 1024,
+ sizeof(u_int32_t), GFP_KERNEL);
+ if (!icode->gpr_map)
+ goto __err_gpr;
+ controls = kcalloc(SND_EMU10K1_GPR_CONTROLS,
+ sizeof(*controls), GFP_KERNEL);
+ 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 */
+ for (i = 0; i < 512; i++)
+ set_bit(i, icode->gpr_valid);
+
+ /* clear TRAM data & address lines */
+ for (i = 0; i < 256; i++)
+ set_bit(i, icode->tram_valid);
+
+ strcpy(icode->name, "Audigy DSP code for ALSA");
+ ptr = 0;
+ nctl = 0;
+ gpr = stereo_mix + 10;
+ gpr_map[gpr++] = 0x00007fff;
+ gpr_map[gpr++] = 0x00008000;
+ gpr_map[gpr++] = 0x0000ffff;
+ bit_shifter16 = gpr;
+
+ /* stop FX processor */
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, (emu->fx8010.dbg = 0) | A_DBG_SINGLE_STEP);
+
+#if 1
+ /* PCM front Playback Volume (independent from stereo mix)
+ * playback = 0 + ( gpr * FXBUS_PCM_LEFT_FRONT >> 31)
+ * where gpr contains attenuation from corresponding mixer control
+ * (snd_emu10k1_init_stereo_control)
+ */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, 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, iMAC0, 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, iMAC0, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT));
+ A_OP(icode, &ptr, iMAC0, 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;
+
+ /*
+ * inputs
+ */
+#define A_ADD_VOLUME_IN(var,vol,input) \
+A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input))
+
+ /* emu1212 DSP 0 and DSP 1 Capture */
+ if (emu->card_capabilities->emu_model) {
+ if (emu->card_capabilities->ca0108_chip) {
+ /* Note:JCD:No longer bit shift lower 16bits to upper 16bits of 32bit value. */
+ A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x0), A_C_00000001);
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_GPR(tmp));
+ A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x1), A_C_00000001);
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr), A_GPR(tmp));
+ } else {
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0));
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_P16VIN(0x1));
+ }
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0);
+ gpr += 2;
+ }
+ /* 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), 0xcd, 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, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix));
+ A_OP(icode, &ptr, iMAC0, 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), 0xcd, A_GPR(stereo_mix+1));
+ A_OP(icode, &ptr, iMAC0, 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, iMAC0, 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, iMAC0, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix));
+ A_OP(icode, &ptr, iMAC0, 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
+ */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), A_GPR(playback + 0), A_C_00000000, A_C_00000000); /* left */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), A_GPR(playback + 1), A_C_00000000, A_C_00000000); /* right */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), A_GPR(playback + 2), A_C_00000000, A_C_00000000); /* rear left */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), A_GPR(playback + 3), A_C_00000000, A_C_00000000); /* rear right */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), A_GPR(playback + 4), A_C_00000000, A_C_00000000); /* center */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), A_GPR(playback + 5), A_C_00000000, A_C_00000000); /* LFE */
+ if (emu->card_capabilities->spk71) {
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 6), A_GPR(playback + 6), A_C_00000000, A_C_00000000); /* side left */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 7), A_GPR(playback + 7), A_C_00000000, A_C_00000000); /* side right */
+ }
+
+
+ ctl = &controls[nctl + 0];
+ ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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 = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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;
+
+#define BASS_GPR 0x8c
+#define TREBLE_GPR 0x96
+
+ 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;
+ }
+ }
+ 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 = 0xb0 + (z * 8) + (j * 4);
+ l = 0xe0 + (z * 8) + (j * 4);
+ d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + 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;
+ }
+ }
+ nctl += 2;
+
+#undef BASS_GPR
+#undef TREBLE_GPR
+
+ for (z = 0; z < 8; z++) {
+ A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0);
+ A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0);
+ A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1);
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+ }
+ snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0);
+ gpr += 2;
+
+ /* Master volume (will be renamed later) */
+ for (z = 0; z < 8; z++)
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS));
+ snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* analog speakers */
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ if (emu->card_capabilities->spk71)
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6 + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+ /* headphone */
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+ /* digital outputs */
+ /* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */
+ 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 + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000);
+ } else {
+ A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000);
+ }
+ }
+ }
+
+ /* 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 + SND_EMU10K1_PLAYBACK_CHANNELS + 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 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+ /* ADC buffer */
+#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+#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) {
+ if (emu->card_capabilities->ca0108_chip) {
+ dev_info(emu->card->dev, "EMU2 inputs on\n");
+ for (z = 0; z < 0x10; z++) {
+ snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp,
+ bit_shifter16,
+ A3_EMU32IN(z),
+ A_FXBUS2(z*2) );
+ }
+ } else {
+ dev_info(emu->card->dev, "EMU inputs on\n");
+ /* Capture 16 (originally 8) channels of S32_LE sound */
+
+ /*
+ 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 > tmp) {
+ 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
+ */
+
+/* when volume = max, then copy only to avoid volume modification */
+/* with iMAC0 (negative values) */
+static void _volume(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+ OP(icode, ptr, iMAC0, dst, C_00000000, src, vol);
+ OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+ OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000001);
+ OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000);
+}
+static void _volume_add(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+ OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+ OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+ OP(icode, ptr, iMACINT0, dst, dst, src, C_00000001);
+ OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001);
+ OP(icode, ptr, iMAC0, dst, dst, src, vol);
+}
+static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+ OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+ OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+ OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000);
+ OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001);
+ OP(icode, ptr, iMAC0, dst, C_00000000, 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_out(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;
+ 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(sizeof(*icode), GFP_KERNEL);
+ if (!icode)
+ return err;
+
+ icode->gpr_map = kcalloc(256 + 160 + 160 + 2 * 512,
+ sizeof(u_int32_t), GFP_KERNEL);
+ if (!icode->gpr_map)
+ goto __err_gpr;
+
+ controls = kcalloc(SND_EMU10K1_GPR_CONTROLS,
+ sizeof(struct snd_emu10k1_fx8010_control_gpr),
+ GFP_KERNEL);
+ if (!controls)
+ goto __err_ctrls;
+
+ ipcm = kzalloc(sizeof(*ipcm), GFP_KERNEL);
+ 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 */
+ for (i = 0; i < 256; i++)
+ set_bit(i, icode->gpr_valid);
+
+ /* clear TRAM data & address lines */
+ for (i = 0; i < 160; i++)
+ set_bit(i, icode->tram_valid);
+
+ strcpy(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 * 2);
+ gpr = capture + SND_EMU10K1_CAPTURE_CHANNELS;
+ tmp = 0x88; /* we need 4 temporary GPR */
+ /* from 0x8c to 0xff is the area for tone control */
+
+ /* stop FX processor */
+ snd_emu10k1_ptr_write(emu, DBG, 0, (emu->fx8010.dbg = 0) | EMU10K1_DBG_SINGLE_STEP);
+
+ /*
+ * Process FX Buses
+ */
+ OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000004);
+ 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_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000004);
+
+ /* 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_00000002);
+ 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<<EXTIN_AC97_L)|(1<<EXTIN_AC97_R))) {
+ /* AC'97 Playback Volume */
+ VOLUME_ADDIN(icode, &ptr, playback + 0, EXTIN_AC97_L, gpr); gpr++;
+ VOLUME_ADDIN(icode, &ptr, playback + 1, EXTIN_AC97_R, gpr); gpr++;
+ snd_emu10k1_init_stereo_control(controls + i++, "AC97 Playback Volume", gpr-2, 0);
+ /* AC'97 Capture Volume */
+ VOLUME_ADDIN(icode, &ptr, capture + 0, EXTIN_AC97_L, gpr); gpr++;
+ VOLUME_ADDIN(icode, &ptr, capture + 1, EXTIN_AC97_R, gpr); gpr++;
+ snd_emu10k1_init_stereo_control(controls + i++, "AC97 Capture Volume", gpr-2, 100);
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_SPDIF_CD_L)|(1<<EXTIN_SPDIF_CD_R))) {
+ /* IEC958 TTL Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_SPDIF_CD_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("TTL ",PLAYBACK,VOLUME), gpr, 0);
+ gpr += 2;
+
+ /* IEC958 TTL Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_SPDIF_CD_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("TTL ",CAPTURE,VOLUME), gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("TTL ",CAPTURE,SWITCH), gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_ZOOM_L)|(1<<EXTIN_ZOOM_R))) {
+ /* Zoom Video Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_ZOOM_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Zoom Video Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* Zoom Video Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_ZOOM_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Zoom Video Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Zoom Video Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_TOSLINK_L)|(1<<EXTIN_TOSLINK_R))) {
+ /* IEC958 Optical Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_TOSLINK_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("LiveDrive ",PLAYBACK,VOLUME), gpr, 0);
+ gpr += 2;
+
+ /* IEC958 Optical Capture Volume */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_TOSLINK_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("LiveDrive ",CAPTURE,VOLUME), gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("LiveDrive ",CAPTURE,SWITCH), gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_LINE1_L)|(1<<EXTIN_LINE1_R))) {
+ /* Line LiveDrive Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_LINE1_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Line LiveDrive Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* Line LiveDrive Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_LINE1_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Line LiveDrive Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Line LiveDrive Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_COAX_SPDIF_L)|(1<<EXTIN_COAX_SPDIF_R))) {
+ /* IEC958 Coax Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_COAX_SPDIF_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("Coaxial ",PLAYBACK,VOLUME), gpr, 0);
+ gpr += 2;
+
+ /* IEC958 Coax Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_COAX_SPDIF_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("Coaxial ",CAPTURE,VOLUME), gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("Coaxial ",CAPTURE,SWITCH), gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_LINE2_L)|(1<<EXTIN_LINE2_R))) {
+ /* Line LiveDrive Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_LINE2_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Line2 LiveDrive Playback Volume", gpr, 0);
+ controls[i-1].id.index = 1;
+ gpr += 2;
+
+ /* Line LiveDrive Capture Volume */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_LINE2_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Line2 LiveDrive Capture Volume", gpr, 0);
+ controls[i-1].id.index = 1;
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Line2 LiveDrive Capture Switch", gpr + 2, 0);
+ controls[i-1].id.index = 1;
+ gpr += 4;
+ }
+
+ /*
+ * Process tone control
+ */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), GPR(playback + 0), C_00000000, C_00000000); /* left */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), GPR(playback + 1), C_00000000, C_00000000); /* right */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), GPR(playback + 2), C_00000000, C_00000000); /* rear left */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), GPR(playback + 3), C_00000000, C_00000000); /* rear right */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), GPR(playback + 4), C_00000000, C_00000000); /* center */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), GPR(playback + 5), C_00000000, C_00000000); /* LFE */
+
+ ctl = &controls[i + 0];
+ ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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 = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(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;
+ }
+ }
+ 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 + SND_EMU10K1_PLAYBACK_CHANNELS + 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;
+ }
+ }
+ i += 2;
+
+#undef BASS_GPR
+#undef TREBLE_GPR
+
+ for (z = 0; z < 6; z++) {
+ SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0);
+ SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0);
+ SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1);
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+ }
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0);
+ gpr += 2;
+
+ /*
+ * Process outputs
+ */
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_AC97_L)|(1<<EXTOUT_AC97_R))) {
+ /* AC'97 Playback Volume */
+
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), C_00000000, C_00000000);
+ }
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_TOSLINK_L)|(1<<EXTOUT_TOSLINK_R))) {
+ /* IEC958 Optical Raw Playback Switch */
+
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, 8 + z, gpr + z);
+ SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
+ SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_TOSLINK_L + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ADC_CAP_L + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+#endif
+ }
+
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0);
+ gpr += 2;
+ }
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_HEADPHONE_L)|(1<<EXTOUT_HEADPHONE_R))) {
+ /* Headphone Playback Volume */
+
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4 + z, gpr + 2 + z);
+ SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 2 + z);
+ SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+ OP(icode, &ptr, iACC3, GPR(tmp + 0), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+ VOLUME_OUT(icode, &ptr, EXTOUT_HEADPHONE_L + z, tmp + 0, gpr + z);
+ }
+
+ snd_emu10k1_init_stereo_control(controls + i++, "Headphone Playback Volume", gpr + 0, 0);
+ controls[i-1].id.index = 1; /* AC'97 can have also Headphone control */
+ snd_emu10k1_init_mono_onoff_control(controls + i++, "Headphone Center Playback Switch", gpr + 2, 0);
+ controls[i-1].id.index = 1;
+ snd_emu10k1_init_mono_onoff_control(controls + i++, "Headphone LFE Playback Switch", gpr + 3, 0);
+ controls[i-1].id.index = 1;
+
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_REAR_L)|(1<<EXTOUT_REAR_R)))
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_REAR_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2 + z), C_00000000, C_00000000);
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_AC97_REAR_L)|(1<<EXTOUT_AC97_REAR_R)))
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_REAR_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2 + z), C_00000000, C_00000000);
+
+ if (emu->fx8010.extout_mask & (1<<EXTOUT_AC97_CENTER)) {
+#ifndef EMU10K1_CENTER_LFE_FROM_FRONT
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_CENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ACENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), C_00000000, C_00000000);
+#else
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_CENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ACENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), C_00000000, C_00000000);
+#endif
+ }
+
+ if (emu->fx8010.extout_mask & (1<<EXTOUT_AC97_LFE)) {
+#ifndef EMU10K1_CENTER_LFE_FROM_FRONT
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_LFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ALFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), C_00000000, C_00000000);
+#else
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_LFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ALFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), C_00000000, C_00000000);
+#endif
+ }
+
+#ifndef EMU10K1_CAPTURE_DIGITAL_OUT
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ADC_CAP_L + z), GPR(capture + z), C_00000000, C_00000000);
+#endif
+
+ if (emu->fx8010.extout_mask & (1<<EXTOUT_MIC_CAP))
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_MIC_CAP), GPR(capture + 2), C_00000000, C_00000000);
+
+ /* EFX capture - capture the 16 EXTINS */
+ if (emu->card_capabilities->sblive51) {
+ /* On the Live! 5.1, FXBUS2(1) and FXBUS(2) are shared with 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 AC97L and R to FX capture 14 and 15, SPDIF CD in to FX capture
+ * 0 and 3, then 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 channels.
+ */
+ OP(icode, &ptr, iACC3, FXBUS2(14), C_00000000, C_00000000, EXTIN(0));
+ OP(icode, &ptr, iACC3, FXBUS2(15), C_00000000, C_00000000, EXTIN(1));
+ OP(icode, &ptr, iACC3, FXBUS2(0), C_00000000, C_00000000, EXTIN(2));
+ OP(icode, &ptr, iACC3, FXBUS2(3), C_00000000, C_00000000, EXTIN(3));
+ for (z = 4; z < 14; z++)
+ OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z));
+ } 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);
+
+ if ((err = snd_emu10k1_fx8010_tram_setup(emu, ipcm->buffer_size)) < 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;
+ spin_lock_irq(&emu->emu_lock);
+ outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG);
+ spin_unlock_irq(&emu->emu_lock);
+ snd_emu10k1_ptr_write(emu, TCB, 0, 0);
+ snd_emu10k1_ptr_write(emu, TCBS, 0, 0);
+ 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);
+ spin_lock_irq(&emu->emu_lock);
+ outl(inl(emu->port + HCFG) & ~HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG);
+ spin_unlock_irq(&emu->emu_lock);
+ }
+
+ 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 fxbus_mask, 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 = fxbuses;
+ extin = emu->audigy ? audigy_ins : creative_ins;
+ extout = emu->audigy ? audigy_outs : creative_outs;
+ fxbus_mask = emu->fx8010.fxbus_mask;
+ extin_mask = emu->fx8010.extin_mask;
+ extout_mask = emu->fx8010.extout_mask;
+ for (res = 0; res < 16; res++, fxbus++, extin++, extout++) {
+ copy_string(info->fxbus_names[res], fxbus_mask & (1 << res) ? *fxbus : NULL, "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;
+ mutex_lock(&emu->fx8010.lock);
+ res = snd_emu10k1_fx8010_tram_setup(emu, addr);
+ mutex_unlock(&emu->fx8010.lock);
+ 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 (addr > 0x1ff)
+ return -EINVAL;
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | addr);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | addr);
+ udelay(10);
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | A_DBG_STEP_ADDR | addr);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | 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;
+
+ if ((err = snd_hwdep_new(emu->card, "FX8010", device, &hw)) < 0)
+ return err;
+ strcpy(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..8a6cbe67e
--- /dev/null
+++ b/sound/pci/emu10k1/emumixer.c
@@ -0,0 +1,2220 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ * Takashi Iwai <tiwai@suse.de>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips / mixer routines
+ * Multichannel PCM support Copyright (c) Lee Revell <rlrevell@joe-job.com>
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ * Added EMU 1010 support.
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+
+#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 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);
+ unsigned long flags;
+
+ /* Limit: emu->spdif_bits */
+ if (idx >= 3)
+ return -EINVAL;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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;
+}
+
+/*
+ * Items labels in enum mixer controls assigning source data to
+ * each destination
+ */
+static const char * const emu1010_src_texts[] = {
+ "Silence",
+ "Dock Mic A",
+ "Dock Mic B",
+ "Dock ADC1 Left",
+ "Dock ADC1 Right",
+ "Dock ADC2 Left",
+ "Dock ADC2 Right",
+ "Dock ADC3 Left",
+ "Dock ADC3 Right",
+ "0202 ADC Left",
+ "0202 ADC Right",
+ "0202 SPDIF Left",
+ "0202 SPDIF Right",
+ "ADAT 0",
+ "ADAT 1",
+ "ADAT 2",
+ "ADAT 3",
+ "ADAT 4",
+ "ADAT 5",
+ "ADAT 6",
+ "ADAT 7",
+ "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",
+};
+
+/* 1616(m) cardbus */
+
+static const char * const emu1616_src_texts[] = {
+ "Silence",
+ "Dock Mic A",
+ "Dock Mic B",
+ "Dock ADC1 Left",
+ "Dock ADC1 Right",
+ "Dock ADC2 Left",
+ "Dock ADC2 Right",
+ "Dock SPDIF Left",
+ "Dock SPDIF Right",
+ "ADAT 0",
+ "ADAT 1",
+ "ADAT 2",
+ "ADAT 3",
+ "ADAT 4",
+ "ADAT 5",
+ "ADAT 6",
+ "ADAT 7",
+ "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",
+};
+
+
+/*
+ * List of data sources available for each destination
+ */
+static const unsigned int emu1010_src_regs[] = {
+ EMU_SRC_SILENCE,/* 0 */
+ EMU_SRC_DOCK_MIC_A1, /* 1 */
+ EMU_SRC_DOCK_MIC_B1, /* 2 */
+ EMU_SRC_DOCK_ADC1_LEFT1, /* 3 */
+ EMU_SRC_DOCK_ADC1_RIGHT1, /* 4 */
+ EMU_SRC_DOCK_ADC2_LEFT1, /* 5 */
+ EMU_SRC_DOCK_ADC2_RIGHT1, /* 6 */
+ EMU_SRC_DOCK_ADC3_LEFT1, /* 7 */
+ EMU_SRC_DOCK_ADC3_RIGHT1, /* 8 */
+ EMU_SRC_HAMOA_ADC_LEFT1, /* 9 */
+ EMU_SRC_HAMOA_ADC_RIGHT1, /* 10 */
+ EMU_SRC_HANA_SPDIF_LEFT1, /* 11 */
+ EMU_SRC_HANA_SPDIF_RIGHT1, /* 12 */
+ EMU_SRC_HANA_ADAT, /* 13 */
+ EMU_SRC_HANA_ADAT+1, /* 14 */
+ EMU_SRC_HANA_ADAT+2, /* 15 */
+ EMU_SRC_HANA_ADAT+3, /* 16 */
+ EMU_SRC_HANA_ADAT+4, /* 17 */
+ EMU_SRC_HANA_ADAT+5, /* 18 */
+ EMU_SRC_HANA_ADAT+6, /* 19 */
+ EMU_SRC_HANA_ADAT+7, /* 20 */
+ EMU_SRC_ALICE_EMU32A, /* 21 */
+ EMU_SRC_ALICE_EMU32A+1, /* 22 */
+ EMU_SRC_ALICE_EMU32A+2, /* 23 */
+ EMU_SRC_ALICE_EMU32A+3, /* 24 */
+ EMU_SRC_ALICE_EMU32A+4, /* 25 */
+ EMU_SRC_ALICE_EMU32A+5, /* 26 */
+ EMU_SRC_ALICE_EMU32A+6, /* 27 */
+ EMU_SRC_ALICE_EMU32A+7, /* 28 */
+ EMU_SRC_ALICE_EMU32A+8, /* 29 */
+ EMU_SRC_ALICE_EMU32A+9, /* 30 */
+ EMU_SRC_ALICE_EMU32A+0xa, /* 31 */
+ EMU_SRC_ALICE_EMU32A+0xb, /* 32 */
+ EMU_SRC_ALICE_EMU32A+0xc, /* 33 */
+ EMU_SRC_ALICE_EMU32A+0xd, /* 34 */
+ EMU_SRC_ALICE_EMU32A+0xe, /* 35 */
+ EMU_SRC_ALICE_EMU32A+0xf, /* 36 */
+ EMU_SRC_ALICE_EMU32B, /* 37 */
+ EMU_SRC_ALICE_EMU32B+1, /* 38 */
+ EMU_SRC_ALICE_EMU32B+2, /* 39 */
+ EMU_SRC_ALICE_EMU32B+3, /* 40 */
+ EMU_SRC_ALICE_EMU32B+4, /* 41 */
+ EMU_SRC_ALICE_EMU32B+5, /* 42 */
+ EMU_SRC_ALICE_EMU32B+6, /* 43 */
+ EMU_SRC_ALICE_EMU32B+7, /* 44 */
+ EMU_SRC_ALICE_EMU32B+8, /* 45 */
+ EMU_SRC_ALICE_EMU32B+9, /* 46 */
+ EMU_SRC_ALICE_EMU32B+0xa, /* 47 */
+ EMU_SRC_ALICE_EMU32B+0xb, /* 48 */
+ EMU_SRC_ALICE_EMU32B+0xc, /* 49 */
+ EMU_SRC_ALICE_EMU32B+0xd, /* 50 */
+ EMU_SRC_ALICE_EMU32B+0xe, /* 51 */
+ EMU_SRC_ALICE_EMU32B+0xf, /* 52 */
+};
+
+/* 1616(m) cardbus */
+static const unsigned int emu1616_src_regs[] = {
+ EMU_SRC_SILENCE,
+ EMU_SRC_DOCK_MIC_A1,
+ EMU_SRC_DOCK_MIC_B1,
+ EMU_SRC_DOCK_ADC1_LEFT1,
+ EMU_SRC_DOCK_ADC1_RIGHT1,
+ EMU_SRC_DOCK_ADC2_LEFT1,
+ EMU_SRC_DOCK_ADC2_RIGHT1,
+ EMU_SRC_MDOCK_SPDIF_LEFT1,
+ EMU_SRC_MDOCK_SPDIF_RIGHT1,
+ EMU_SRC_MDOCK_ADAT,
+ EMU_SRC_MDOCK_ADAT+1,
+ EMU_SRC_MDOCK_ADAT+2,
+ EMU_SRC_MDOCK_ADAT+3,
+ EMU_SRC_MDOCK_ADAT+4,
+ EMU_SRC_MDOCK_ADAT+5,
+ EMU_SRC_MDOCK_ADAT+6,
+ EMU_SRC_MDOCK_ADAT+7,
+ 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,
+};
+
+/*
+ * Data destinations - physical EMU outputs.
+ * Each destination has an enum mixer control to choose a data source
+ */
+static const unsigned int emu1010_output_dst[] = {
+ EMU_DST_DOCK_DAC1_LEFT1, /* 0 */
+ EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */
+ EMU_DST_DOCK_DAC2_LEFT1, /* 2 */
+ EMU_DST_DOCK_DAC2_RIGHT1, /* 3 */
+ EMU_DST_DOCK_DAC3_LEFT1, /* 4 */
+ EMU_DST_DOCK_DAC3_RIGHT1, /* 5 */
+ EMU_DST_DOCK_DAC4_LEFT1, /* 6 */
+ EMU_DST_DOCK_DAC4_RIGHT1, /* 7 */
+ EMU_DST_DOCK_PHONES_LEFT1, /* 8 */
+ EMU_DST_DOCK_PHONES_RIGHT1, /* 9 */
+ EMU_DST_DOCK_SPDIF_LEFT1, /* 10 */
+ EMU_DST_DOCK_SPDIF_RIGHT1, /* 11 */
+ EMU_DST_HANA_SPDIF_LEFT1, /* 12 */
+ EMU_DST_HANA_SPDIF_RIGHT1, /* 13 */
+ EMU_DST_HAMOA_DAC_LEFT1, /* 14 */
+ EMU_DST_HAMOA_DAC_RIGHT1, /* 15 */
+ EMU_DST_HANA_ADAT, /* 16 */
+ EMU_DST_HANA_ADAT+1, /* 17 */
+ EMU_DST_HANA_ADAT+2, /* 18 */
+ EMU_DST_HANA_ADAT+3, /* 19 */
+ EMU_DST_HANA_ADAT+4, /* 20 */
+ EMU_DST_HANA_ADAT+5, /* 21 */
+ EMU_DST_HANA_ADAT+6, /* 22 */
+ EMU_DST_HANA_ADAT+7, /* 23 */
+};
+
+/* 1616(m) cardbus */
+static const unsigned int emu1616_output_dst[] = {
+ EMU_DST_DOCK_DAC1_LEFT1,
+ EMU_DST_DOCK_DAC1_RIGHT1,
+ EMU_DST_DOCK_DAC2_LEFT1,
+ EMU_DST_DOCK_DAC2_RIGHT1,
+ EMU_DST_DOCK_DAC3_LEFT1,
+ EMU_DST_DOCK_DAC3_RIGHT1,
+ EMU_DST_MDOCK_SPDIF_LEFT1,
+ EMU_DST_MDOCK_SPDIF_RIGHT1,
+ EMU_DST_MDOCK_ADAT,
+ EMU_DST_MDOCK_ADAT+1,
+ EMU_DST_MDOCK_ADAT+2,
+ EMU_DST_MDOCK_ADAT+3,
+ EMU_DST_MDOCK_ADAT+4,
+ EMU_DST_MDOCK_ADAT+5,
+ EMU_DST_MDOCK_ADAT+6,
+ EMU_DST_MDOCK_ADAT+7,
+ EMU_DST_MANA_DAC_LEFT,
+ EMU_DST_MANA_DAC_RIGHT,
+};
+
+/*
+ * Data destinations - HANA outputs going to Alice2 (audigy) for
+ * capture (EMU32 + I2S links)
+ * Each destination has an enum mixer control to choose a data source
+ */
+static const unsigned int 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,
+ 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 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);
+
+ if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616)
+ return snd_ctl_enum_info(uinfo, 1, 49, emu1616_src_texts);
+ else
+ return snd_ctl_enum_info(uinfo, 1, 53, emu1010_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);
+ unsigned int channel;
+
+ channel = (kcontrol->private_value) & 0xff;
+ /* Limit: emu1010_output_dst, emu->emu1010.output_source */
+ if (channel >= 24 ||
+ (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+ channel >= 18))
+ 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);
+ unsigned int val;
+ unsigned int channel;
+
+ val = ucontrol->value.enumerated.item[0];
+ if (val >= 53 ||
+ (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+ val >= 49))
+ return -EINVAL;
+ channel = (kcontrol->private_value) & 0xff;
+ /* Limit: emu1010_output_dst, emu->emu1010.output_source */
+ if (channel >= 24 ||
+ (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+ channel >= 18))
+ return -EINVAL;
+ if (emu->emu1010.output_source[channel] == val)
+ return 0;
+ emu->emu1010.output_source[channel] = val;
+ if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616)
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ emu1616_output_dst[channel], emu1616_src_regs[val]);
+ else
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ emu1010_output_dst[channel], emu1010_src_regs[val]);
+ return 1;
+}
+
+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);
+ unsigned int channel;
+
+ channel = (kcontrol->private_value) & 0xff;
+ /* Limit: emu1010_input_dst, emu->emu1010.input_source */
+ if (channel >= 22)
+ 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);
+ unsigned int val;
+ unsigned int channel;
+
+ val = ucontrol->value.enumerated.item[0];
+ if (val >= 53 ||
+ (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+ val >= 49))
+ return -EINVAL;
+ channel = (kcontrol->private_value) & 0xff;
+ /* Limit: emu1010_input_dst, emu->emu1010.input_source */
+ if (channel >= 22)
+ return -EINVAL;
+ if (emu->emu1010.input_source[channel] == val)
+ return 0;
+ emu->emu1010.input_source[channel] = val;
+ if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616)
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ emu1010_input_dst[channel], emu1616_src_regs[val]);
+ else
+ snd_emu1010_fpga_link_dst_src_write(emu,
+ emu1010_input_dst[channel], emu1010_src_regs[val]);
+ return 1;
+}
+
+#define EMU1010_SOURCE_OUTPUT(xname,chid) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .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, \
+ .private_value = chid \
+}
+
+static const struct snd_kcontrol_new snd_emu1010_output_enum_ctls[] = {
+ EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0),
+ EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1),
+ EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2),
+ EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3),
+ EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4),
+ EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5),
+ EMU1010_SOURCE_OUTPUT("Dock DAC4 Left Playback Enum", 6),
+ EMU1010_SOURCE_OUTPUT("Dock DAC4 Right Playback Enum", 7),
+ EMU1010_SOURCE_OUTPUT("Dock Phones Left Playback Enum", 8),
+ EMU1010_SOURCE_OUTPUT("Dock Phones Right Playback Enum", 9),
+ EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 0xa),
+ EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 0xb),
+ EMU1010_SOURCE_OUTPUT("1010 SPDIF Left Playback Enum", 0xc),
+ EMU1010_SOURCE_OUTPUT("1010 SPDIF Right Playback Enum", 0xd),
+ EMU1010_SOURCE_OUTPUT("0202 DAC Left Playback Enum", 0xe),
+ EMU1010_SOURCE_OUTPUT("0202 DAC Right Playback Enum", 0xf),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 0 Playback Enum", 0x10),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 1 Playback Enum", 0x11),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 2 Playback Enum", 0x12),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 3 Playback Enum", 0x13),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 4 Playback Enum", 0x14),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 5 Playback Enum", 0x15),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 6 Playback Enum", 0x16),
+ EMU1010_SOURCE_OUTPUT("1010 ADAT 7 Playback Enum", 0x17),
+};
+
+
+/* 1616(m) cardbus */
+static const struct snd_kcontrol_new snd_emu1616_output_enum_ctls[] = {
+ EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0),
+ EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1),
+ EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2),
+ EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3),
+ EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4),
+ EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5),
+ EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 6),
+ EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 7),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 0 Playback Enum", 8),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 1 Playback Enum", 9),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 2 Playback Enum", 0xa),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 3 Playback Enum", 0xb),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 4 Playback Enum", 0xc),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 5 Playback Enum", 0xd),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 6 Playback Enum", 0xe),
+ EMU1010_SOURCE_OUTPUT("Dock ADAT 7 Playback Enum", 0xf),
+ EMU1010_SOURCE_OUTPUT("Mana DAC Left Playback Enum", 0x10),
+ EMU1010_SOURCE_OUTPUT("Mana DAC Right Playback Enum", 0x11),
+};
+
+
+#define EMU1010_SOURCE_INPUT(xname,chid) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .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, \
+ .private_value = chid \
+}
+
+static const struct snd_kcontrol_new snd_emu1010_input_enum_ctls[] = {
+ EMU1010_SOURCE_INPUT("DSP 0 Capture Enum", 0),
+ EMU1010_SOURCE_INPUT("DSP 1 Capture Enum", 1),
+ EMU1010_SOURCE_INPUT("DSP 2 Capture Enum", 2),
+ EMU1010_SOURCE_INPUT("DSP 3 Capture Enum", 3),
+ EMU1010_SOURCE_INPUT("DSP 4 Capture Enum", 4),
+ EMU1010_SOURCE_INPUT("DSP 5 Capture Enum", 5),
+ EMU1010_SOURCE_INPUT("DSP 6 Capture Enum", 6),
+ EMU1010_SOURCE_INPUT("DSP 7 Capture Enum", 7),
+ EMU1010_SOURCE_INPUT("DSP 8 Capture Enum", 8),
+ EMU1010_SOURCE_INPUT("DSP 9 Capture Enum", 9),
+ EMU1010_SOURCE_INPUT("DSP A Capture Enum", 0xa),
+ EMU1010_SOURCE_INPUT("DSP B Capture Enum", 0xb),
+ EMU1010_SOURCE_INPUT("DSP C Capture Enum", 0xc),
+ EMU1010_SOURCE_INPUT("DSP D Capture Enum", 0xd),
+ EMU1010_SOURCE_INPUT("DSP E Capture Enum", 0xe),
+ EMU1010_SOURCE_INPUT("DSP F Capture Enum", 0xf),
+ EMU1010_SOURCE_INPUT("DSP 10 Capture Enum", 0x10),
+ EMU1010_SOURCE_INPUT("DSP 11 Capture Enum", 0x11),
+ EMU1010_SOURCE_INPUT("DSP 12 Capture Enum", 0x12),
+ EMU1010_SOURCE_INPUT("DSP 13 Capture Enum", 0x13),
+ EMU1010_SOURCE_INPUT("DSP 14 Capture Enum", 0x14),
+ EMU1010_SOURCE_INPUT("DSP 15 Capture Enum", 0x15),
+};
+
+
+
+#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 = kcontrol->private_value & 0xff;
+ 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 = kcontrol->private_value & 0xff;
+ unsigned int val, cache;
+ val = ucontrol->value.integer.value[0];
+ cache = emu->emu1010.adc_pads;
+ if (val == 1)
+ cache = cache | mask;
+ else
+ cache = cache & ~mask;
+ if (cache != emu->emu1010.adc_pads) {
+ snd_emu1010_fpga_write(emu, EMU_HANA_ADC_PADS, cache );
+ emu->emu1010.adc_pads = cache;
+ }
+
+ return 0;
+}
+
+
+
+#define EMU1010_ADC_PADS(xname,chid) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_emu1010_adc_pads_info, \
+ .get = snd_emu1010_adc_pads_get, \
+ .put = snd_emu1010_adc_pads_put, \
+ .private_value = chid \
+}
+
+static const struct snd_kcontrol_new snd_emu1010_adc_pads[] = {
+ EMU1010_ADC_PADS("ADC1 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD1),
+ EMU1010_ADC_PADS("ADC2 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD2),
+ EMU1010_ADC_PADS("ADC3 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD3),
+ EMU1010_ADC_PADS("ADC1 14dB PAD 0202 Capture Switch", EMU_HANA_0202_ADC_PAD1),
+};
+
+#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 = kcontrol->private_value & 0xff;
+ 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 = kcontrol->private_value & 0xff;
+ unsigned int val, cache;
+ val = ucontrol->value.integer.value[0];
+ cache = emu->emu1010.dac_pads;
+ if (val == 1)
+ cache = cache | mask;
+ else
+ cache = cache & ~mask;
+ if (cache != emu->emu1010.dac_pads) {
+ snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, cache );
+ emu->emu1010.dac_pads = cache;
+ }
+
+ return 0;
+}
+
+
+
+#define EMU1010_DAC_PADS(xname,chid) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_emu1010_dac_pads_info, \
+ .get = snd_emu1010_dac_pads_get, \
+ .put = snd_emu1010_dac_pads_put, \
+ .private_value = chid \
+}
+
+static const struct snd_kcontrol_new snd_emu1010_dac_pads[] = {
+ EMU1010_DAC_PADS("DAC1 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD1),
+ EMU1010_DAC_PADS("DAC2 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD2),
+ EMU1010_DAC_PADS("DAC3 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD3),
+ EMU1010_DAC_PADS("DAC4 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD4),
+ EMU1010_DAC_PADS("DAC1 0202 14dB PAD Playback Switch", EMU_HANA_0202_DAC_PAD1),
+};
+
+
+static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[4] = {
+ "44100", "48000", "SPDIF", "ADAT"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 4, texts);
+}
+
+static int snd_emu1010_internal_clock_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.internal_clock;
+ return 0;
+}
+
+static int snd_emu1010_internal_clock_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;
+
+ val = ucontrol->value.enumerated.item[0] ;
+ /* Limit: uinfo->value.enumerated.items = 4; */
+ if (val >= 4)
+ return -EINVAL;
+ change = (emu->emu1010.internal_clock != val);
+ if (change) {
+ emu->emu1010.internal_clock = val;
+ switch (val) {
+ case 0:
+ /* 44100 */
+ /* Mute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+ /* Default fallback clock 48kHz */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_44_1K );
+ /* Word Clock source, Internal 44.1kHz x1 */
+ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+ EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X );
+ /* Set LEDs on Audio Dock */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2,
+ EMU_HANA_DOCK_LEDS_2_44K | EMU_HANA_DOCK_LEDS_2_LOCK );
+ /* Allow DLL to settle */
+ msleep(10);
+ /* Unmute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+ break;
+ case 1:
+ /* 48000 */
+ /* Mute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+ /* Default fallback clock 48kHz */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K );
+ /* Word Clock source, Internal 48kHz x1 */
+ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+ EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X );
+ /* Set LEDs on Audio Dock */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2,
+ EMU_HANA_DOCK_LEDS_2_48K | EMU_HANA_DOCK_LEDS_2_LOCK );
+ /* Allow DLL to settle */
+ msleep(10);
+ /* Unmute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+ break;
+
+ case 2: /* Take clock from S/PDIF IN */
+ /* Mute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+ /* Default fallback clock 48kHz */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K );
+ /* Word Clock source, sync to S/PDIF input */
+ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+ EMU_HANA_WCLOCK_HANA_SPDIF_IN | EMU_HANA_WCLOCK_1X );
+ /* Set LEDs on Audio Dock */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2,
+ EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK );
+ /* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */
+ /* Allow DLL to settle */
+ msleep(10);
+ /* Unmute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+ break;
+
+ case 3:
+ /* Take clock from ADAT IN */
+ /* Mute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+ /* Default fallback clock 48kHz */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K );
+ /* Word Clock source, sync to ADAT input */
+ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+ EMU_HANA_WCLOCK_HANA_ADAT_IN | EMU_HANA_WCLOCK_1X );
+ /* Set LEDs on Audio Dock */
+ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK );
+ /* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */
+ /* Allow DLL to settle */
+ msleep(10);
+ /* Unmute all */
+ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+
+
+ break;
+ }
+ }
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_emu1010_internal_clock =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Clock Internal Rate",
+ .count = 1,
+ .info = snd_emu1010_internal_clock_info,
+ .get = snd_emu1010_internal_clock_get,
+ .put = snd_emu1010_internal_clock_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 : 0) |
+ (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
+ snd_emu1010_fpga_write(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 : 0) |
+ (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
+ snd_emu1010_fpga_write(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;
+ u32 gpio;
+ int change = 0;
+ unsigned long flags;
+ 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 */
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ gpio = inl(emu->port + A_IOCFG);
+ if (source_id==0)
+ outl(gpio | 0x4, emu->port + A_IOCFG);
+ else
+ outl(gpio & ~0x4, emu->port + A_IOCFG);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+ 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 ngain;
+ 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;
+ ogain = emu->i2c_capture_volume[source_id][0]; /* Left */
+ ngain = ucontrol->value.integer.value[0];
+ if (ngain > 0xff)
+ return 0;
+ if (ogain != ngain) {
+ if (emu->i2c_capture_source == source_id)
+ snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff) );
+ emu->i2c_capture_volume[source_id][0] = ngain;
+ change = 1;
+ }
+ ogain = emu->i2c_capture_volume[source_id][1]; /* Right */
+ ngain = ucontrol->value.integer.value[1];
+ if (ngain > 0xff)
+ return 0;
+ if (ogain != ngain) {
+ if (emu->i2c_capture_source == source_id)
+ snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff));
+ emu->i2c_capture_volume[source_id][1] = ngain;
+ change = 1;
+ }
+
+ return change;
+}
+
+#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_audigy_i2c_volume_info, \
+ .get = snd_audigy_i2c_volume_get, \
+ .put = snd_audigy_i2c_volume_put, \
+ .tlv = { .p = snd_audigy_db_scale2 }, \
+ .private_value = chid \
+}
+
+
+static const struct snd_kcontrol_new snd_audigy_i2c_volume_ctls[] = {
+ I2C_VOLUME("Mic Capture Volume", 0),
+ I2C_VOLUME("Line Capture Volume", 0)
+};
+
+#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;
+ unsigned long flags;
+
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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;
+ unsigned long flags;
+
+ 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;
+ }
+
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ reg = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ tmp = reg & ~A_SPDIF_RATE_MASK;
+ tmp |= val;
+ if ((change = (tmp != reg)))
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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;
+ unsigned long flags;
+
+ /* 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);
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ change = val != emu->spdif_bits[idx];
+ if (change) {
+ snd_emu10k1_ptr_write(emu, SPCS0 + idx, 0, val);
+ emu->spdif_bits[idx] = val;
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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(emu, A_FXRT1, voice,
+ snd_emu10k1_compose_audigy_fxrt1(route));
+ snd_emu10k1_ptr_write(emu, A_FXRT2, voice,
+ snd_emu10k1_compose_audigy_fxrt2(route));
+ } 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) {
+ unsigned int val = ((unsigned int)volume[4] << 24) |
+ ((unsigned int)volume[5] << 16) |
+ ((unsigned int)volume[6] << 8) |
+ (unsigned int)volume[7];
+ snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, val);
+ }
+}
+
+/* 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)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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) {
+ if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+ update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+ &mix->send_routing[1][0]);
+ update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number,
+ &mix->send_routing[2][0]);
+ } else if (mix->epcm->voices[0]) {
+ update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+ &mix->send_routing[0][0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3*num_efx; idx++)
+ ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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) {
+ if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+ update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+ &mix->send_volume[1][0]);
+ update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number,
+ &mix->send_volume[2][0]);
+ } else if (mix->epcm->voices[0]) {
+ update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+ &mix->send_volume[0][0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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 = 0xffff;
+ 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)];
+ unsigned long flags;
+ int idx;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3; idx++)
+ ucontrol->value.integer.value[idx] = mix->attn[idx];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3; idx++) {
+ val = ucontrol->value.integer.value[idx] & 0xffff;
+ if (mix->attn[idx] != val) {
+ mix->attn[idx] = val;
+ change = 1;
+ }
+ }
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+ 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[1]->number, mix->attn[2]);
+ } else if (mix->epcm->voices[0]) {
+ snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < num_efx; idx++)
+ ucontrol->value.integer.value[idx] =
+ mix->send_routing[0][idx] & mask;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < num_efx; idx++)
+ ucontrol->value.integer.value[idx] = mix->send_volume[0][idx];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ 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]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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 = 0xffff;
+ 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)];
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ ucontrol->value.integer.value[0] = mix->attn[0];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ val = ucontrol->value.integer.value[0] & 0xffff;
+ 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]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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] = inl(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)
+{
+ unsigned long flags;
+ 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;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ if ( emu->card_capabilities->i2c_adc) {
+ /* Do nothing for Audigy 2 ZS Notebook */
+ } else if (emu->audigy) {
+ reg = inl(emu->port + A_IOCFG);
+ val = sw ? A_IOCFG_GPOUT0 : 0;
+ change = (reg & A_IOCFG_GPOUT0) != val;
+ if (change) {
+ reg &= ~A_IOCFG_GPOUT0;
+ reg |= val;
+ outl(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);
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ 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));
+ strcpy(id.name, name);
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_remove_id(card, &id);
+}
+
+static struct snd_kcontrol *ctl_find(struct snd_card *card, const char *name)
+{
+ struct snd_ctl_elem_id sid;
+ memset(&sid, 0, sizeof(sid));
+ strcpy(sid.name, name);
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_find_id(card, &sid);
+}
+
+static int rename_ctl(struct snd_card *card, const char *src, const char *dst)
+{
+ struct snd_kcontrol *kctl = ctl_find(card, src);
+ if (kctl) {
+ strcpy(kctl->id.name, dst);
+ return 0;
+ }
+ return -ENOENT;
+}
+
+int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
+ int pcm_device, int multi_device)
+{
+ int err, pcm;
+ 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,
+ };
+
+ if ((err = snd_ac97_bus(emu->card, 0, &ops, NULL, &pbus)) < 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;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &emu->ac97)) < 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 <James@superbug.demon.co.uk>
+ * Voluspa <voluspa@comhem.se>
+ */
+ 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)
+ strcpy(emu->card->mixername, "EMU APS");
+ else if (emu->audigy)
+ strcpy(emu->card->mixername, "SB Audigy");
+ else
+ strcpy(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");
+ }
+ if ((kctl = emu->ctl_send_routing = snd_ctl_new1(&snd_emu10k1_send_routing_control, emu)) == NULL)
+ return -ENOMEM;
+ kctl->id.device = pcm_device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu)) == NULL)
+ return -ENOMEM;
+ kctl->id.device = pcm_device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu)) == NULL)
+ return -ENOMEM;
+ kctl->id.device = pcm_device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ if ((kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu)) == NULL)
+ return -ENOMEM;
+ kctl->id.device = multi_device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ if ((kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu)) == NULL)
+ return -ENOMEM;
+ kctl->id.device = multi_device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ if ((kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu)) == NULL)
+ return -ENOMEM;
+ kctl->id.device = multi_device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ /* initialize the routing and volume table for each pcm playback stream */
+ for (pcm = 0; pcm < 32; pcm++) {
+ struct snd_emu10k1_pcm_mixer *mix;
+ int v;
+
+ mix = &emu->pcm_mixer[pcm];
+ mix->epcm = NULL;
+
+ for (v = 0; v < 4; v++)
+ mix->send_routing[0][v] =
+ mix->send_routing[1][v] =
+ mix->send_routing[2][v] = v;
+
+ 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] = 0xffff;
+ }
+
+ /* initialize the routing and volume table for the multichannel playback stream */
+ for (pcm = 0; pcm < NUM_EFX_PLAYBACK; pcm++) {
+ struct snd_emu10k1_pcm_mixer *mix;
+ int v;
+
+ mix = &emu->efx_pcm_mixer[pcm];
+ mix->epcm = NULL;
+
+ mix->send_routing[0][0] = pcm;
+ mix->send_routing[0][1] = (pcm == 0) ? 1 : 0;
+ for (v = 0; v < 2; v++)
+ mix->send_routing[0][2+v] = 13+v;
+ if (emu->audigy)
+ for (v = 0; v < 4; v++)
+ mix->send_routing[0][4+v] = 60+v;
+
+ memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+ mix->send_volume[0][0] = 255;
+
+ mix->attn[0] = 0xffff;
+ }
+
+ if (! emu->card_capabilities->ecard) { /* FIXME: APS has these controls? */
+ /* sb live! and audigy */
+ if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu)) == NULL)
+ return -ENOMEM;
+ if (!emu->audigy)
+ kctl->id.device = emu->pcm_efx->device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_control, emu)) == NULL)
+ return -ENOMEM;
+ if (!emu->audigy)
+ kctl->id.device = emu->pcm_efx->device;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ }
+
+ if (emu->card_capabilities->emu_model) {
+ ; /* Disable the snd_audigy_spdif_shared_spdif */
+ } else if (emu->audigy) {
+ if ((kctl = snd_ctl_new1(&snd_audigy_shared_spdif, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+#if 0
+ if ((kctl = snd_ctl_new1(&snd_audigy_spdif_output_rate, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+#endif
+ } else if (! emu->card_capabilities->ecard) {
+ /* sb live! */
+ if ((kctl = snd_ctl_new1(&snd_emu10k1_shared_spdif, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ }
+ if (emu->card_capabilities->ca0151_chip) { /* P16V */
+ if ((err = snd_p16v_mixer(emu)))
+ return err;
+ }
+
+ if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) {
+ /* 1616(m) cardbus */
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_emu1616_output_enum_ctls); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1616_output_enum_ctls[i],
+ emu));
+ if (err < 0)
+ return err;
+ }
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_input_enum_ctls[i],
+ emu));
+ if (err < 0)
+ return err;
+ }
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads) - 2; i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_adc_pads[i], emu));
+ if (err < 0)
+ return err;
+ }
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads) - 2; i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_dac_pads[i], emu));
+ if (err < 0)
+ return err;
+ }
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_internal_clock, emu));
+ if (err < 0)
+ return err;
+ 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;
+
+ } else if (emu->card_capabilities->emu_model) {
+ /* all other e-mu cards for now */
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_output_enum_ctls); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_output_enum_ctls[i],
+ emu));
+ if (err < 0)
+ return err;
+ }
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_input_enum_ctls[i],
+ emu));
+ if (err < 0)
+ return err;
+ }
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_adc_pads[i], emu));
+ if (err < 0)
+ return err;
+ }
+ for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_dac_pads[i], emu));
+ if (err < 0)
+ return err;
+ }
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_emu1010_internal_clock, emu));
+ if (err < 0)
+ return err;
+ 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;
+ }
+
+ if ( emu->card_capabilities->i2c_adc) {
+ int i;
+
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_capture_source, emu));
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(snd_audigy_i2c_volume_ctls); i++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_volume_ctls[i], emu));
+ 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..b62c95150
--- /dev/null
+++ b/sound/pci/emu10k1/emumpu401.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of EMU10K1 MPU-401 in UART mode
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+#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;
+ }
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&midi->input_lock);
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&midi->output_lock);
+}
+
+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)
+{
+ unsigned long flags;
+ int timeout, ok;
+
+ spin_lock_irqsave(&midi->input_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&midi->input_lock, flags);
+ 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;
+ unsigned long flags;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1_MIDI_MODE_INPUT;
+ midi->substream_input = substream;
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1))
+ goto error_out;
+ if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+ goto error_out;
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+
+error_out:
+ return -EIO;
+}
+
+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;
+ unsigned long flags;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1_MIDI_MODE_OUTPUT;
+ midi->substream_output = substream;
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1))
+ goto error_out;
+ if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+ goto error_out;
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+
+error_out:
+ return -EIO;
+}
+
+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;
+ unsigned long flags;
+ int err = 0;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ 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)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ err = snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return err;
+}
+
+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;
+ unsigned long flags;
+ int err = 0;
+
+ emu = midi->emu;
+ if (snd_BUG_ON(!emu))
+ return -ENXIO;
+ spin_lock_irqsave(&midi->open_lock, flags);
+ 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)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ err = snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return err;
+}
+
+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;
+ unsigned long flags;
+
+ 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 */
+ spin_lock_irqsave(&midi->output_lock, flags);
+ 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 */
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ return;
+ }
+ mpu401_write_data(emu, midi, byte);
+ max--;
+ } else {
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ 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;
+
+ if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
+ return err;
+ midi->emu = emu;
+ spin_lock_init(&midi->open_lock);
+ spin_lock_init(&midi->input_lock);
+ spin_lock_init(&midi->output_lock);
+ strcpy(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;
+
+ if ((err = emu10k1_midi_init(emu, midi, 0, "EMU10K1 MPU-401 (UART)")) < 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;
+ if ((err = emu10k1_midi_init(emu, midi, 0, "Audigy MPU-401 (UART)")) < 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;
+ if ((err = emu10k1_midi_init(emu, midi, 1, "Audigy MPU-401 #2")) < 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..1c4388b84
--- /dev/null
+++ b/sound/pci/emu10k1/emupcm.c
@@ -0,0 +1,1826 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips / PCM routines
+ * Multichannel PCM support Copyright (c) Lee Revell <rlrevell@joe-job.com>
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static void snd_emu10k1_pcm_interrupt(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_voice *voice)
+{
+ struct snd_emu10k1_pcm *epcm;
+
+ if ((epcm = voice->epcm) == NULL)
+ 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 snd_pcm_uframes_t snd_emu10k1_efx_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;
+ unsigned int ptr;
+
+ if (!epcm->running)
+ return 0;
+ ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+ ptr += runtime->buffer_size;
+ ptr -= epcm->ccca_start_addr;
+ ptr %= runtime->buffer_size;
+
+ return ptr;
+}
+
+static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices)
+{
+ int err, i;
+
+ if (epcm->voices[1] != NULL && voices < 2) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
+ epcm->voices[1] = NULL;
+ }
+ for (i = 0; i < voices; i++) {
+ if (epcm->voices[i] == NULL)
+ break;
+ }
+ if (i == voices)
+ return 0; /* already allocated */
+
+ for (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;
+ }
+ }
+ err = snd_emu10k1_voice_alloc(epcm->emu,
+ epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
+ voices,
+ &epcm->voices[0]);
+
+ if (err < 0)
+ return err;
+ epcm->voices[0]->epcm = epcm;
+ if (voices > 1) {
+ for (i = 1; i < voices; i++) {
+ epcm->voices[i] = &epcm->emu->voices[(epcm->voices[0]->number + i) % NUM_G];
+ epcm->voices[i]->epcm = epcm;
+ }
+ }
+ if (epcm->extra == NULL) {
+ err = snd_emu10k1_voice_alloc(epcm->emu,
+ epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
+ 1,
+ &epcm->extra);
+ if (err < 0) {
+ /*
+ dev_dbg(emu->card->dev, "pcm_channel_alloc: "
+ "failed extra: voices=%d, frame=%d\n",
+ voices, frame);
+ */
+ for (i = 0; i < voices; i++) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+ epcm->voices[i] = NULL;
+ }
+ return err;
+ }
+ epcm->extra->epcm = epcm;
+ epcm->extra->interrupt = snd_emu10k1_pcm_interrupt;
+ }
+ return 0;
+}
+
+static const unsigned int capture_period_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_period_sizes = {
+ .count = 31,
+ .list = capture_period_sizes,
+ .mask = 0
+};
+
+static const unsigned int capture_rates[8] = {
+ 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_capture_rates = {
+ .count = 8,
+ .list = capture_rates,
+ .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; /* really supported? */
+ 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 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;
+}
+
+/*
+ * calculate cache invalidate size
+ *
+ * stereo: channel is stereo
+ * w_16: using 16bit samples
+ *
+ * returns: cache invalidate size in samples
+ */
+static inline int emu10k1_ccis(int stereo, int w_16)
+{
+ if (w_16) {
+ return stereo ? 24 : 26;
+ } else {
+ return stereo ? 24*2 : 26*2;
+ }
+}
+
+static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu,
+ int master, int extra,
+ struct snd_emu10k1_voice *evoice,
+ unsigned int start_addr,
+ unsigned int end_addr,
+ struct snd_emu10k1_pcm_mixer *mix)
+{
+ struct snd_pcm_substream *substream = evoice->epcm->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int silent_page, tmp;
+ int voice, stereo, w_16;
+ unsigned char send_amount[8];
+ unsigned char send_routing[8];
+ unsigned long flags;
+ unsigned int pitch_target;
+ unsigned int ccis;
+
+ voice = evoice->number;
+ stereo = runtime->channels == 2;
+ w_16 = snd_pcm_format_width(runtime->format) == 16;
+
+ if (!extra && stereo) {
+ start_addr >>= 1;
+ end_addr >>= 1;
+ }
+ if (w_16) {
+ start_addr >>= 1;
+ end_addr >>= 1;
+ }
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+
+ /* volume parameters */
+ if (extra) {
+ memset(send_routing, 0, sizeof(send_routing));
+ send_routing[0] = 0;
+ send_routing[1] = 1;
+ send_routing[2] = 2;
+ send_routing[3] = 3;
+ memset(send_amount, 0, sizeof(send_amount));
+ } else {
+ /* mono, left, right (master voice = left) */
+ tmp = stereo ? (master ? 1 : 2) : 0;
+ memcpy(send_routing, &mix->send_routing[tmp][0], 8);
+ memcpy(send_amount, &mix->send_volume[tmp][0], 8);
+ }
+
+ ccis = emu10k1_ccis(stereo, w_16);
+
+ if (master) {
+ evoice->epcm->ccca_start_addr = start_addr + ccis;
+ if (extra) {
+ start_addr += ccis;
+ end_addr += ccis + emu->delay_pcm_irq;
+ }
+ if (stereo && !extra) {
+ snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK);
+ snd_emu10k1_ptr_write(emu, CPF, (voice + 1), CPF_STEREO_MASK);
+ } else {
+ snd_emu10k1_ptr_write(emu, CPF, voice, 0);
+ }
+ }
+
+ /* setup routing */
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXRT1, voice,
+ snd_emu10k1_compose_audigy_fxrt1(send_routing));
+ snd_emu10k1_ptr_write(emu, A_FXRT2, voice,
+ snd_emu10k1_compose_audigy_fxrt2(send_routing));
+ snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice,
+ ((unsigned int)send_amount[4] << 24) |
+ ((unsigned int)send_amount[5] << 16) |
+ ((unsigned int)send_amount[6] << 8) |
+ (unsigned int)send_amount[7]);
+ } else
+ snd_emu10k1_ptr_write(emu, FXRT, voice,
+ snd_emu10k1_compose_send_routing(send_routing));
+ /* Stop CA */
+ /* Assumption that PT is already 0 so no harm overwriting */
+ snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]);
+ snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24));
+ snd_emu10k1_ptr_write(emu, PSST, voice,
+ (start_addr + (extra ? emu->delay_pcm_irq : 0)) |
+ (send_amount[2] << 24));
+ if (emu->card_capabilities->emu_model)
+ pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */
+ else
+ pitch_target = emu10k1_calc_pitch_target(runtime->rate);
+ if (extra)
+ snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr |
+ emu10k1_select_interprom(pitch_target) |
+ (w_16 ? 0 : CCCA_8BITSELECT));
+ else
+ snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) |
+ emu10k1_select_interprom(pitch_target) |
+ (w_16 ? 0 : CCCA_8BITSELECT));
+ /* Clear filter delay memory */
+ snd_emu10k1_ptr_write(emu, Z1, voice, 0);
+ snd_emu10k1_ptr_write(emu, Z2, voice, 0);
+ /* invalidate maps */
+ silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0);
+ snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page);
+ snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page);
+ /* modulation envelope */
+ snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, ATKHLDM, voice, 0);
+ snd_emu10k1_ptr_write(emu, DCYSUSM, voice, 0x007f);
+ snd_emu10k1_ptr_write(emu, LFOVAL1, voice, 0x8000);
+ snd_emu10k1_ptr_write(emu, LFOVAL2, voice, 0x8000);
+ snd_emu10k1_ptr_write(emu, FMMOD, voice, 0);
+ snd_emu10k1_ptr_write(emu, TREMFRQ, voice, 0);
+ snd_emu10k1_ptr_write(emu, FM2FRQ2, voice, 0);
+ snd_emu10k1_ptr_write(emu, ENVVAL, voice, 0x8000);
+ /* volume envelope */
+ snd_emu10k1_ptr_write(emu, ATKHLDV, voice, 0x7f7f);
+ snd_emu10k1_ptr_write(emu, ENVVOL, voice, 0x0000);
+ /* filter envelope */
+ snd_emu10k1_ptr_write(emu, PEFE_FILTERAMOUNT, voice, 0x7f);
+ /* pitch envelope */
+ snd_emu10k1_ptr_write(emu, PEFE_PITCHAMOUNT, voice, 0);
+
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+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 err;
+
+ if ((err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params))) < 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;
+ }
+ if (epcm->voices[1]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
+ epcm->voices[1] = NULL;
+ }
+ if (epcm->voices[0]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
+ epcm->voices[0] = NULL;
+ }
+ 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_efx_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;
+ int i;
+
+ 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;
+ }
+ for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+ if (epcm->voices[i]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+ epcm->voices[i] = NULL;
+ }
+ }
+ 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;
+ unsigned int start_addr, end_addr;
+
+ start_addr = epcm->start_addr;
+ end_addr = snd_pcm_lib_period_bytes(substream);
+ if (runtime->channels == 2) {
+ start_addr >>= 1;
+ end_addr >>= 1;
+ }
+ end_addr += start_addr;
+ snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+ start_addr, end_addr, NULL);
+ start_addr = epcm->start_addr;
+ end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+ snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+ start_addr, end_addr,
+ &emu->pcm_mixer[substream->number]);
+ if (epcm->voices[1])
+ snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1],
+ 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, end_addr;
+ unsigned int channel_size;
+ int i;
+
+ start_addr = epcm->start_addr;
+ end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+
+ /*
+ * the kX driver leaves some space between voices
+ */
+ channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK;
+
+ snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+ start_addr, start_addr + (channel_size / 2), NULL);
+
+ /* only difference with the master voice is we use it for the pointer */
+ snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+ start_addr, start_addr + channel_size,
+ &emu->efx_pcm_mixer[0]);
+
+ start_addr += channel_size;
+ for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
+ snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i],
+ 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 = NUM_EFX_PLAYBACK,
+ .channels_max = NUM_EFX_PLAYBACK,
+ .buffer_bytes_max = (64*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (64*1024),
+ .periods_min = 2,
+ .periods_max = 2,
+ .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->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+ snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+ } 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_period_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) {
+ 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(runtime->rate) :
+ snd_emu10k1_capture_rate_reg(runtime->rate);
+ }
+ return 0;
+}
+
+static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int extra, struct snd_emu10k1_voice *evoice)
+{
+ struct snd_pcm_runtime *runtime;
+ unsigned int voice, stereo, i, ccis, cra = 64, cs, sample;
+
+ if (evoice == NULL)
+ return;
+ runtime = evoice->epcm->substream->runtime;
+ voice = evoice->number;
+ stereo = (!extra && runtime->channels == 2);
+ sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080;
+ ccis = emu10k1_ccis(stereo, sample == 0);
+ /* set cs to 2 * number of cache registers beside the invalidated */
+ cs = (sample == 0) ? (32-ccis) : (64-ccis+1) >> 1;
+ if (cs > 16) cs = 16;
+ for (i = 0; i < cs; i++) {
+ snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample);
+ if (stereo) {
+ snd_emu10k1_ptr_write(emu, CD0 + i, voice + 1, sample);
+ }
+ }
+ /* reset cache */
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0);
+ snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra);
+ if (stereo) {
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0);
+ snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra);
+ }
+ /* fill cache */
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, ccis);
+ if (stereo) {
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice+1, ccis);
+ }
+}
+
+static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice,
+ int master, int extra,
+ struct snd_emu10k1_pcm_mixer *mix)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int attn, vattn;
+ unsigned int voice, tmp;
+
+ if (evoice == NULL) /* skip second voice for mono */
+ return;
+ substream = evoice->epcm->substream;
+ runtime = substream->runtime;
+ voice = evoice->number;
+
+ attn = extra ? 0 : 0x00ff;
+ tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0;
+ vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0;
+ snd_emu10k1_ptr_write(emu, IFATN, voice, attn);
+ snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | 0xffff);
+ snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | 0xffff);
+ snd_emu10k1_ptr_write(emu, DCYSUSV, voice, 0x7f7f);
+ snd_emu10k1_voice_clear_loop_stop(emu, voice);
+}
+
+static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, int master, int extra)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int voice, pitch, pitch_target;
+
+ if (evoice == NULL) /* skip second voice for mono */
+ return;
+ substream = evoice->epcm->substream;
+ runtime = substream->runtime;
+ voice = evoice->number;
+
+ pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8;
+ if (emu->card_capabilities->emu_model)
+ pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */
+ else
+ pitch_target = emu10k1_calc_pitch_target(runtime->rate);
+ snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target);
+ if (master || evoice->epcm->type == PLAYBACK_EFX)
+ snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target);
+ snd_emu10k1_ptr_write(emu, IP, voice, pitch);
+ if (extra)
+ snd_emu10k1_voice_intr_enable(emu, voice);
+}
+
+static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice)
+{
+ unsigned int voice;
+
+ if (evoice == NULL)
+ return;
+ voice = evoice->number;
+ snd_emu10k1_voice_intr_disable(emu, voice);
+ snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0);
+ snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0);
+ snd_emu10k1_ptr_write(emu, IFATN, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, IP, voice, 0);
+}
+
+static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_pcm *epcm,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_runtime *runtime)
+{
+ unsigned int ptr, period_pos;
+
+ /* try to sychronize the current position for the interrupt
+ source voice */
+ period_pos = runtime->status->hw_ptr - runtime->hw_ptr_interrupt;
+ period_pos %= runtime->period_size;
+ ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->extra->number);
+ ptr &= ~0x00ffffff;
+ ptr |= epcm->ccca_start_addr + period_pos;
+ snd_emu10k1_ptr_write(emu, CCCA, epcm->extra->number, ptr);
+}
+
+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;
+ int result = 0;
+
+ /*
+ dev_dbg(emu->card->dev,
+ "trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n",
+ (int)emu, cmd, substream->ops->pointer(substream))
+ */
+ spin_lock(&emu->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra); /* do we need this? */
+ snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[0]);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
+ snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime);
+ mix = &emu->pcm_mixer[substream->number];
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, 0, mix);
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, 0, mix);
+ snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+ epcm->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ epcm->running = 0;
+ snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]);
+ snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]);
+ snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+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;
+ int result = 0;
+
+ spin_lock(&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(emu, A_FXWC1, 0, epcm->capture_cr_val);
+ snd_emu10k1_ptr_write(emu, A_FXWC2, 0, epcm->capture_cr_val2);
+ 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(emu, A_FXWC1, 0, 0);
+ snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+ } else
+ snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ result = -EINVAL;
+ }
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+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;
+ unsigned int ptr;
+
+ if (!epcm->running)
+ return 0;
+ ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+#if 0 /* Perex's code */
+ ptr += runtime->buffer_size;
+ ptr -= epcm->ccca_start_addr;
+ ptr %= runtime->buffer_size;
+#else /* EMU10K1 Open Source code from Creative */
+ if (ptr < epcm->ccca_start_addr)
+ ptr += runtime->buffer_size - epcm->ccca_start_addr;
+ else {
+ ptr -= epcm->ccca_start_addr;
+ if (ptr >= runtime->buffer_size)
+ ptr -= runtime->buffer_size;
+ }
+#endif
+ /*
+ 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 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;
+ int i;
+ int result = 0;
+
+ spin_lock(&emu->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* prepare voices */
+ for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+ snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[i]);
+ }
+ snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL);
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, 0,
+ &emu->efx_pcm_mixer[0]);
+ for (i = 1; i < NUM_EFX_PLAYBACK; i++)
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, 0,
+ &emu->efx_pcm_mixer[i]);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+ for (i = 1; i < NUM_EFX_PLAYBACK; i++)
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0);
+ epcm->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ epcm->running = 0;
+ for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+ snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
+ }
+ snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&emu->reg_lock);
+ 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_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .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,
+ .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_44100 | SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000,
+ .rate_min = 44100,
+ .rate_max = 192000,
+ .channels_min = 8,
+ .channels_max = 8,
+ .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_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;
+
+ epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->type = PLAYBACK_EFX;
+ epcm->substream = substream;
+
+ emu->pcm_playback_efx_substream = substream;
+
+ runtime->private_data = epcm;
+ runtime->private_free = snd_emu10k1_pcm_free_substream;
+ runtime->hw = snd_emu10k1_efx_playback;
+
+ for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+ mix = &emu->efx_pcm_mixer[i];
+ mix->send_routing[0][0] = i;
+ memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+ mix->send_volume[0][0] = 255;
+ mix->attn[0] = 0xffff;
+ 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(sizeof(*epcm), GFP_KERNEL);
+ 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;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
+ kfree(epcm);
+ return err;
+ }
+ if ((err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX)) < 0) {
+ kfree(epcm);
+ return err;
+ }
+ if (emu->card_capabilities->emu_model && emu->emu1010.internal_clock == 0)
+ sample_rate = 44100;
+ else
+ sample_rate = 48000;
+ err = snd_pcm_hw_rule_noresample(runtime, sample_rate);
+ if (err < 0) {
+ kfree(epcm);
+ return err;
+ }
+ mix = &emu->pcm_mixer[substream->number];
+ for (i = 0; i < 4; 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] = 0xffff;
+ mix->epcm = epcm;
+ snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1);
+ return 0;
+}
+
+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(sizeof(*epcm), GFP_KERNEL);
+ 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;
+ emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt;
+ emu->pcm_capture_substream = substream;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates);
+ 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(sizeof(*epcm), GFP_KERNEL);
+ 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;
+ runtime->hw.channels_min = 1;
+ emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt;
+ emu->pcm_capture_mic_substream = substream;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+ 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;
+
+ epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+ 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;
+ runtime->hw.rates = SNDRV_PCM_RATE_48000;
+ runtime->hw.rate_min = runtime->hw.rate_max = 48000;
+ spin_lock_irq(&emu->reg_lock);
+ if (emu->card_capabilities->emu_model) {
+ /* Nb. of channels has been increased to 16 */
+ /* TODO
+ * SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE
+ * 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 = 44100,
+ * rate_max = 192000,
+ * channels_min = 16,
+ * channels_max = 16,
+ * Need to add mixer control to fix sample rate
+ *
+ * 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
+ *
+ */
+#if 1
+ switch (emu->emu1010.internal_clock) {
+ case 0:
+ /* For 44.1kHz */
+ runtime->hw.rates = SNDRV_PCM_RATE_44100;
+ runtime->hw.rate_min = runtime->hw.rate_max = 44100;
+ runtime->hw.channels_min =
+ runtime->hw.channels_max = 16;
+ break;
+ case 1:
+ /* For 48kHz */
+ runtime->hw.rates = SNDRV_PCM_RATE_48000;
+ runtime->hw.rate_min = runtime->hw.rate_max = 48000;
+ runtime->hw.channels_min =
+ runtime->hw.channels_max = 16;
+ break;
+ }
+#endif
+#if 0
+ /* For 96kHz */
+ runtime->hw.rates = SNDRV_PCM_RATE_96000;
+ runtime->hw.rate_min = runtime->hw.rate_max = 96000;
+ runtime->hw.channels_min = runtime->hw.channels_max = 4;
+#endif
+#if 0
+ /* For 192kHz */
+ runtime->hw.rates = SNDRV_PCM_RATE_192000;
+ runtime->hw.rate_min = runtime->hw.rate_max = 192000;
+ runtime->hw.channels_min = runtime->hw.channels_max = 2;
+#endif
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+ /* efx_voices_mask[0] is expected to be zero
+ * efx_voices_mask[1] is expected to have 32bits set
+ */
+ } else {
+ 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];
+ spin_unlock_irq(&emu->reg_lock);
+ emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt;
+ emu->pcm_capture_efx_substream = substream;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+ 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_efx_playback_hw_free,
+ .prepare = snd_emu10k1_efx_playback_prepare,
+ .trigger = snd_emu10k1_efx_playback_trigger,
+ .pointer = snd_emu10k1_efx_playback_pointer,
+};
+
+int snd_emu10k1_pcm(struct snd_emu10k1 *emu, int device)
+{
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+ int err;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1", device, 32, 1, &pcm)) < 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;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1", device, 1, 0, &pcm)) < 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;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1 mic", device, 0, 1, &pcm)) < 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;
+ strcpy(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;
+
+ spin_lock_irq(&emu->reg_lock);
+ for (idx = 0; idx < nefx; idx++)
+ ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0;
+ spin_unlock_irq(&emu->reg_lock);
+ 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 nefxb = emu->audigy ? 7 : 6;
+ 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++;
+ }
+
+ for (idx = 0; idx < nefxb; idx++)
+ if (1 << idx == bits)
+ break;
+
+ if (idx >= nefxb)
+ return -EINVAL;
+
+ spin_lock_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];
+ spin_unlock_irq(&emu->reg_lock);
+ 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(emu, emu->gpr_base + pcm->gpr_running, 0, 0); /* reset */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0); /* reset */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_size, 0, runtime->buffer_size);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_ptr, 0, 0); /* reset ptr number */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_count, 0, runtime->period_size);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_tmpcount, 0, runtime->period_size);
+ 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 = 0;
+
+ spin_lock(&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)
+ goto __err;
+ 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:
+ result = -EINVAL;
+ break;
+ }
+ __err:
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+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;
+ spin_lock_irq(&emu->reg_lock);
+ if (pcm->valid == 0) {
+ spin_unlock_irq(&emu->reg_lock);
+ return -ENODEV;
+ }
+ pcm->opened = 1;
+ spin_unlock_irq(&emu->reg_lock);
+ 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];
+
+ spin_lock_irq(&emu->reg_lock);
+ pcm->opened = 0;
+ spin_unlock_irq(&emu->reg_lock);
+ 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;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1 efx", device, 8, 1, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+
+ 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;
+ strcpy(pcm->name, "Multichannel Capture/PT Playback");
+ emu->pcm_efx = pcm;
+
+ /* EFX capture - record the "FXBUS2" channels, by default we connect the EXTINs
+ * to these
+ */
+
+ /* emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A; */
+ if (emu->audigy) {
+ emu->efx_voices_mask[0] = 0;
+ if (emu->card_capabilities->emu_model)
+ /* Pavel Hofman - 32 voices will be used for
+ * capture (write mode) -
+ * each bit = corresponding voice
+ */
+ emu->efx_voices_mask[1] = 0xffffffff;
+ else
+ emu->efx_voices_mask[1] = 0xffff;
+ } else {
+ emu->efx_voices_mask[0] = 0xffff0000;
+ emu->efx_voices_mask[1] = 0;
+ }
+ /* For emu1010, the control has to set 32 upper bits (voices)
+ * out of the 64 bits (voices) to true for the 16-channels capture
+ * to work correctly. Correct A_FXWC2 initial value (0xffffffff)
+ * is already defined but the snd_emu10k1_pcm_efx_voices_mask
+ * control can override this register's value.
+ */
+ 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;
+
+ 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..6e20cca9c
--- /dev/null
+++ b/sound/pci/emu10k1/emuproc.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips / proc interface routines
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ * Added EMU 1010 support.
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#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", (status & SPCS_PROFESSIONAL) ? "yes" : "no");
+ snd_iprintf(buffer, "Not Audio Data : %s\n", (status & SPCS_NOTAUDIODATA) ? "yes" : "no");
+ snd_iprintf(buffer, "Copyright : %s\n", (status & SPCS_COPYRIGHT) ? "yes" : "no");
+ 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", rate & SRCS_SPDIFVALID ? "on" : "off");
+ snd_iprintf(buffer, "S/PDIF Locked : %s\n", rate & SRCS_SPDIFLOCKED ? "on" : "off");
+ snd_iprintf(buffer, "Rate Locked : %s\n", rate & SRCS_RATELOCKED ? "on" : "off");
+ /* 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)
+{
+ /* FIXME - output names are in emufx.c too */
+ static const char * const creative_outs[32] = {
+ /* 00 */ "AC97 Left",
+ /* 01 */ "AC97 Right",
+ /* 02 */ "Optical IEC958 Left",
+ /* 03 */ "Optical IEC958 Right",
+ /* 04 */ "Center",
+ /* 05 */ "LFE",
+ /* 06 */ "Headphone Left",
+ /* 07 */ "Headphone Right",
+ /* 08 */ "Surround Left",
+ /* 09 */ "Surround Right",
+ /* 10 */ "PCM Capture Left",
+ /* 11 */ "PCM Capture Right",
+ /* 12 */ "MIC Capture",
+ /* 13 */ "AC97 Surround Left",
+ /* 14 */ "AC97 Surround Right",
+ /* 15 */ "???",
+ /* 16 */ "???",
+ /* 17 */ "Analog Center",
+ /* 18 */ "Analog LFE",
+ /* 19 */ "???",
+ /* 20 */ "???",
+ /* 21 */ "???",
+ /* 22 */ "???",
+ /* 23 */ "???",
+ /* 24 */ "???",
+ /* 25 */ "???",
+ /* 26 */ "???",
+ /* 27 */ "???",
+ /* 28 */ "???",
+ /* 29 */ "???",
+ /* 30 */ "???",
+ /* 31 */ "???"
+ };
+
+ static const char * const audigy_outs[64] = {
+ /* 00 */ "Digital Front Left",
+ /* 01 */ "Digital Front Right",
+ /* 02 */ "Digital Center",
+ /* 03 */ "Digital LEF",
+ /* 04 */ "Headphone Left",
+ /* 05 */ "Headphone Right",
+ /* 06 */ "Digital Rear Left",
+ /* 07 */ "Digital Rear Right",
+ /* 08 */ "Front Left",
+ /* 09 */ "Front Right",
+ /* 10 */ "Center",
+ /* 11 */ "LFE",
+ /* 12 */ "???",
+ /* 13 */ "???",
+ /* 14 */ "Rear Left",
+ /* 15 */ "Rear Right",
+ /* 16 */ "AC97 Front Left",
+ /* 17 */ "AC97 Front Right",
+ /* 18 */ "ADC Capture Left",
+ /* 19 */ "ADC Capture Right",
+ /* 20 */ "???",
+ /* 21 */ "???",
+ /* 22 */ "???",
+ /* 23 */ "???",
+ /* 24 */ "???",
+ /* 25 */ "???",
+ /* 26 */ "???",
+ /* 27 */ "???",
+ /* 28 */ "???",
+ /* 29 */ "???",
+ /* 30 */ "???",
+ /* 31 */ "???",
+ /* 32 */ "FXBUS2_0",
+ /* 33 */ "FXBUS2_1",
+ /* 34 */ "FXBUS2_2",
+ /* 35 */ "FXBUS2_3",
+ /* 36 */ "FXBUS2_4",
+ /* 37 */ "FXBUS2_5",
+ /* 38 */ "FXBUS2_6",
+ /* 39 */ "FXBUS2_7",
+ /* 40 */ "FXBUS2_8",
+ /* 41 */ "FXBUS2_9",
+ /* 42 */ "FXBUS2_10",
+ /* 43 */ "FXBUS2_11",
+ /* 44 */ "FXBUS2_12",
+ /* 45 */ "FXBUS2_13",
+ /* 46 */ "FXBUS2_14",
+ /* 47 */ "FXBUS2_15",
+ /* 48 */ "FXBUS2_16",
+ /* 49 */ "FXBUS2_17",
+ /* 50 */ "FXBUS2_18",
+ /* 51 */ "FXBUS2_19",
+ /* 52 */ "FXBUS2_20",
+ /* 53 */ "FXBUS2_21",
+ /* 54 */ "FXBUS2_22",
+ /* 55 */ "FXBUS2_23",
+ /* 56 */ "FXBUS2_24",
+ /* 57 */ "FXBUS2_25",
+ /* 58 */ "FXBUS2_26",
+ /* 59 */ "FXBUS2_27",
+ /* 60 */ "FXBUS2_28",
+ /* 61 */ "FXBUS2_29",
+ /* 62 */ "FXBUS2_30",
+ /* 63 */ "FXBUS2_31"
+ };
+
+ struct snd_emu10k1 *emu = entry->private_data;
+ unsigned int val, val1;
+ int nefx = emu->audigy ? 64 : 32;
+ const char * const *outputs = emu->audigy ? audigy_outs : creative_outs;
+ int idx;
+
+ snd_iprintf(buffer, "EMU10K1\n\n");
+ snd_iprintf(buffer, "Card : %s\n",
+ emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative"));
+ 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, "\n");
+ snd_iprintf(buffer, "Effect Send Routing :\n");
+ for (idx = 0; idx < NUM_G; idx++) {
+ val = emu->audigy ?
+ snd_emu10k1_ptr_read(emu, A_FXRT1, idx) :
+ snd_emu10k1_ptr_read(emu, FXRT, idx);
+ val1 = emu->audigy ?
+ snd_emu10k1_ptr_read(emu, A_FXRT2, idx) :
+ 0;
+ if (emu->audigy) {
+ snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i, ",
+ idx,
+ val & 0x3f,
+ (val >> 8) & 0x3f,
+ (val >> 16) & 0x3f,
+ (val >> 24) & 0x3f);
+ snd_iprintf(buffer, "E=%i, F=%i, G=%i, H=%i\n",
+ val1 & 0x3f,
+ (val1 >> 8) & 0x3f,
+ (val1 >> 16) & 0x3f,
+ (val1 >> 24) & 0x3f);
+ } else {
+ snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i\n",
+ idx,
+ (val >> 16) & 0x0f,
+ (val >> 20) & 0x0f,
+ (val >> 24) & 0x0f,
+ (val >> 28) & 0x0f);
+ }
+ }
+ snd_iprintf(buffer, "\nCaptured FX Outputs :\n");
+ for (idx = 0; idx < nefx; idx++) {
+ if (emu->efx_voices_mask[idx/32] & (1 << (idx%32)))
+ snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]);
+ }
+ snd_iprintf(buffer, "\nAll FX Outputs :\n");
+ for (idx = 0; idx < (emu->audigy ? 64 : 32); idx++)
+ snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[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;
+ u32 rate;
+
+ if (emu->card_capabilities->emu_model) {
+ snd_emu1010_fpga_read(emu, 0x38, &value);
+ if ((value & 0x1) == 0) {
+ snd_emu1010_fpga_read(emu, 0x2a, &value);
+ snd_emu1010_fpga_read(emu, 0x2b, &value2);
+ rate = 0x1770000 / (((value << 5) | value2)+1);
+ snd_iprintf(buffer, "ADAT Locked : %u\n", rate);
+ } else {
+ snd_iprintf(buffer, "ADAT Unlocked\n");
+ }
+ snd_emu1010_fpga_read(emu, 0x20, &value);
+ if ((value & 0x4) == 0) {
+ snd_emu1010_fpga_read(emu, 0x28, &value);
+ snd_emu1010_fpga_read(emu, 0x29, &value2);
+ rate = 0x1770000 / (((value << 5) | value2)+1);
+ snd_iprintf(buffer, "SPDIF Locked : %d\n", rate);
+ } else {
+ snd_iprintf(buffer, "SPDIF Unlocked\n");
+ }
+ } 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", val & SRCS_RATELOCKED ? "on" : "off");
+ 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);
+ }
+}
+
+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;
+
+ 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;
+
+ low = snd_emu10k1_efx_read(emu, pc * 2);
+ high = snd_emu10k1_efx_read(emu, pc * 2 + 1);
+ if (emu->audigy)
+ snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
+ (high >> 24) & 0x0f,
+ (high >> 12) & 0x7ff,
+ (high >> 0) & 0x7ff,
+ (low >> 12) & 0x7ff,
+ (low >> 0) & 0x7ff,
+ pc,
+ high, low);
+ else
+ snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
+ (high >> 20) & 0x0f,
+ (high >> 10) & 0x3ff,
+ (high >> 0) & 0x3ff,
+ (low >> 10) & 0x3ff,
+ (low >> 0) & 0x3ff,
+ 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;
+
+ snd_iprintf(buffer, "ch\tuse\tpcm\tefx\tsynth\tmidi\n");
+ for (idx = 0; idx < NUM_G; idx++) {
+ voice = &emu->voices[idx];
+ snd_iprintf(buffer, "%i\t%i\t%i\t%i\t%i\t%i\n",
+ idx,
+ voice->use,
+ voice->pcm,
+ voice->efx,
+ voice->synth,
+ voice->midi);
+ }
+}
+
+#ifdef CONFIG_SND_DEBUG
+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;
+ 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: %08X, %02X\n", i, value, (value >> 8) & 0x7f);
+ }
+}
+
+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;
+ unsigned long flags;
+ int i;
+ snd_iprintf(buffer, "IO Registers:\n\n");
+ for(i = 0; i < 0x40; i+=4) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inl(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ 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;
+ unsigned long flags;
+ char line[64];
+ u32 reg, val;
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if (reg < 0x40 && val <= 0xffffffff) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(val, emu->port + (reg & 0xfffffffc));
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ }
+ }
+}
+
+static unsigned int snd_ptr_read(struct snd_emu10k1 * emu,
+ unsigned int iobase,
+ unsigned int reg,
+ unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + iobase + PTR);
+ val = inl(emu->port + iobase + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_ptr_write(struct snd_emu10k1 *emu,
+ unsigned int iobase,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + iobase + PTR);
+ outl(data, emu->port + iobase + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+
+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++) {
+ if(iobase == 0)
+ value = snd_ptr_read(emu, 0, i, j);
+ else
+ value = snd_ptr_read(emu, 0x20, 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)
+{
+ 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", &reg, &channel_id, &val) != 3)
+ continue;
+ if (reg < 0xa0 && val <= 0xffffffff && channel_id <= 3)
+ 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);
+}
+
+static void snd_emu_proc_ptr_reg_write20(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ snd_emu_proc_ptr_reg_write(entry, buffer, 0x20);
+}
+
+
+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);
+ 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);
+ 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..e15092ce9
--- /dev/null
+++ b/sound/pci/emu10k1/io.c
@@ -0,0 +1,566 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include "p17v.h"
+
+unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+ unsigned int mask;
+
+ mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
+ regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
+
+ if (reg & 0xff000000) {
+ unsigned char size, offset;
+
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+ return (val & mask) >> offset;
+ } else {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ 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 long flags;
+ unsigned int mask;
+
+ if (snd_BUG_ON(!emu))
+ return;
+ mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
+ regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
+
+ if (reg & 0xff000000) {
+ unsigned char size, offset;
+
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+ data = (data << offset) & mask;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ data |= inl(emu->port + DATA) & ~mask;
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ } else {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ }
+}
+
+EXPORT_SYMBOL(snd_emu10k1_ptr_write);
+
+unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu,
+ unsigned int reg,
+ unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + 0x20 + PTR);
+ val = inl(emu->port + 0x20 + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + 0x20 + PTR);
+ outl(data, emu->port + 0x20 + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+int snd_emu10k1_spi_write(struct snd_emu10k1 * emu,
+ unsigned int data)
+{
+ unsigned int reset, set;
+ unsigned int reg, tmp;
+ int n, result;
+ int err = 0;
+
+ /* This function is not re-entrant, so protect against it. */
+ spin_lock(&emu->spi_lock);
+ if (emu->card_capabilities->ca0108_chip)
+ reg = 0x3c; /* PTR20, reg 0x3c */
+ else {
+ /* For other chip types the SPI register
+ * is currently unknown. */
+ err = 1;
+ goto spi_write_exit;
+ }
+ if (data > 0xffff) {
+ /* Only 16bit values allowed */
+ err = 1;
+ goto spi_write_exit;
+ }
+
+ 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 */
+ err = 1;
+ goto spi_write_exit;
+ }
+ snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
+ tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* Write post */
+ err = 0;
+spi_write_exit:
+ spin_unlock(&emu->spi_lock);
+ return err;
+}
+
+/* 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;
+ int err = 0;
+
+ 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. */
+ spin_lock(&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(); */
+ err = -EINVAL;
+ }
+
+ spin_unlock(&emu->i2c_lock);
+ return err;
+}
+
+int snd_emu1010_fpga_write(struct snd_emu10k1 * emu, u32 reg, u32 value)
+{
+ unsigned long flags;
+
+ if (reg > 0x3f)
+ return 1;
+ reg += 0x40; /* 0x40 upwards are registers. */
+ if (value > 0x3f) /* 0 to 0x3f are values */
+ return 1;
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(reg, emu->port + A_IOCFG);
+ udelay(10);
+ outl(reg | 0x80, emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
+ udelay(10);
+ outl(value, emu->port + A_IOCFG);
+ udelay(10);
+ outl(value | 0x80 , emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+ return 0;
+}
+
+int snd_emu1010_fpga_read(struct snd_emu10k1 * emu, u32 reg, u32 *value)
+{
+ unsigned long flags;
+ if (reg > 0x3f)
+ return 1;
+ reg += 0x40; /* 0x40 upwards are registers. */
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(reg, emu->port + A_IOCFG);
+ udelay(10);
+ outl(reg | 0x80, emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
+ udelay(10);
+ *value = ((inl(emu->port + A_IOCFG) >> 8) & 0x7f);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+ return 0;
+}
+
+/* Each Destination has one and only one Source,
+ * but one Source can feed any number of Destinations simultaneously.
+ */
+int snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 * emu, u32 dst, u32 src)
+{
+ snd_emu1010_fpga_write(emu, 0x00, ((dst >> 8) & 0x3f) );
+ snd_emu1010_fpga_write(emu, 0x01, (dst & 0x3f) );
+ snd_emu1010_fpga_write(emu, 0x02, ((src >> 8) & 0x3f) );
+ snd_emu1010_fpga_write(emu, 0x03, (src & 0x3f) );
+
+ return 0;
+}
+
+void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE) | intrenb;
+ outl(enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE) & ~intrenb;
+ outl(enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int sol;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int sol;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ 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);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+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;
+ unsigned long flags;
+ unsigned short val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ val = inw(emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data)
+{
+ struct snd_emu10k1 *emu = ac97->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ outw(data, emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+/*
+ * convert rate to pitch
+ */
+
+unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate)
+{
+ static const u32 logMagTable[128] = {
+ 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
+ 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
+ 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
+ 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
+ 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
+ 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
+ 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
+ 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
+ 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
+ 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
+ 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
+ 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
+ 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
+ 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
+ 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
+ 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
+ };
+ static const char logSlopeTable[128] = {
+ 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
+ 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
+ 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
+ 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
+ 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
+ 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
+ 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
+ 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
+ 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
+ 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
+ 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
+ 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
+ };
+ int i;
+
+ if (rate == 0)
+ return 0; /* Bail out if no leading "1" */
+ rate *= 11185; /* Scale 48000 to 0x20002380 */
+ for (i = 31; i > 0; i--) {
+ if (rate & 0x80000000) { /* Detect leading "1" */
+ return (((unsigned int) (i - 15) << 20) +
+ logMagTable[0x7f & (rate >> 24)] +
+ (0x7f & (rate >> 17)) *
+ logSlopeTable[0x7f & (rate >> 24)]);
+ }
+ rate <<= 1;
+ }
+
+ return 0; /* Should never reach this point */
+}
+
diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c
new file mode 100644
index 000000000..ebb2275ef
--- /dev/null
+++ b/sound/pci/emu10k1/irq.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Routines for IRQ control of EMU10K1 chips
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id)
+{
+ struct snd_emu10k1 *emu = dev_id;
+ unsigned int status, status2, orig_status, orig_status2;
+ int handled = 0;
+ int timeout = 0;
+
+ while (((status = inl(emu->port + IPR)) != 0) && (timeout < 1000)) {
+ timeout++;
+ orig_status = status;
+ handled = 1;
+ if ((status & 0xffffffff) == 0xffffffff) {
+ dev_info(emu->card->dev,
+ "Suspected sound card removal\n");
+ break;
+ }
+ 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) {
+ int voice;
+ int voice_max = status & IPR_CHANNELNUMBERMASK;
+ u32 val;
+ struct snd_emu10k1_voice *pvoice = emu->voices;
+
+ val = snd_emu10k1_ptr_read(emu, CLIPL, 0);
+ 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);
+ 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;
+ }
+ status &= ~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) {
+ while ((status2 = inl(emu->port + IPR2)) != 0) {
+ u32 mask = INTE2_PLAYBACK_CH_0_LOOP; /* Full Loop */
+ struct snd_emu10k1_voice *pvoice = &(emu->p16v_voices[0]);
+ struct snd_emu10k1_voice *cvoice = &(emu->p16v_capture_voice);
+
+ /* dev_dbg(emu->card->dev, "status2=0x%x\n", status2); */
+ orig_status2 = status2;
+ if(status2 & mask) {
+ if(pvoice->use) {
+ snd_pcm_period_elapsed(pvoice->epcm->substream);
+ } else {
+ dev_err(emu->card->dev,
+ "p16v: status: 0x%08x, mask=0x%08x, pvoice=%p, use=%d\n",
+ status2, mask, pvoice,
+ pvoice->use);
+ }
+ }
+ if(status2 & 0x110000) {
+ /* dev_info(emu->card->dev, "capture int found\n"); */
+ if(cvoice->use) {
+ /* dev_info(emu->card->dev, "capture period_elapsed\n"); */
+ snd_pcm_period_elapsed(cvoice->epcm->substream);
+ }
+ }
+ outl(orig_status2, emu->port + IPR2); /* ack all */
+ }
+ status &= ~IPR_P16V;
+ }
+
+ if (status) {
+ unsigned int bits;
+ dev_err(emu->card->dev,
+ "unhandled interrupt: 0x%08x\n", status);
+ //make sure any interrupts we don't handle are disabled:
+ bits = INTE_FXDSPENABLE |
+ INTE_PCIERRORENABLE |
+ INTE_VOLINCRENABLE |
+ INTE_VOLDECRENABLE |
+ INTE_MUTEENABLE |
+ INTE_MICBUFENABLE |
+ INTE_ADCBUFENABLE |
+ INTE_EFXBUFENABLE |
+ INTE_GPSPDIFENABLE |
+ INTE_CDSPDIFENABLE |
+ INTE_INTERVALTIMERENB |
+ INTE_MIDITXENABLE |
+ INTE_MIDIRXENABLE;
+ if (emu->audigy)
+ bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2;
+ snd_emu10k1_intr_disable(emu, bits);
+ }
+ outl(orig_status, emu->port + IPR); /* ack all */
+ }
+ if (timeout == 1000)
+ dev_info(emu->card->dev, "emu10k1 irq routine failure\n");
+
+ return IRQ_RETVAL(handled);
+}
diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c
new file mode 100644
index 000000000..94b8d5b08
--- /dev/null
+++ b/sound/pci/emu10k1/memory.c
@@ -0,0 +1,621 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * EMU10K1 memory page allocation (PTB area)
+ */
+
+#include <linux/pci.h>
+#include <linux/gfp.h>
+#include <linux/time.h>
+#include <linux/mutex.h>
+#include <linux/export.h>
+
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+/* 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 */
+ if ((p = blk->mapped_link.prev) != &emu->mapped_link_head) {
+ q = get_emu10k1_memblk(p, mapped_link);
+ start_page = q->mapped_page + q->pages;
+ } else
+ start_page = 1;
+ if ((p = blk->mapped_link.next) != &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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->memblk_lock, flags);
+ if (blk->mapped_page >= 0) {
+ /* update order link */
+ list_move_tail(&blk->mapped_order_link,
+ &emu->mapped_order_link_head);
+ spin_unlock_irqrestore(&emu->memblk_lock, flags);
+ return 0;
+ }
+ if ((err = map_memblk(emu, blk)) < 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;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&emu->memblk_lock, flags);
+ 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;
+
+ idx = runtime->period_size >= runtime->buffer_size ?
+ (emu->delay_pcm_irq * 2) : 0;
+ mutex_lock(&hdr->block_mutex);
+ blk = search_empty(emu, runtime->dma_bytes + idx);
+ if (blk == NULL) {
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ /* fill buffer addresses but pointers are not stored so that
+ * snd_free_pci_page() is not called in 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);
+ mutex_unlock(&hdr->block_mutex);
+ 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);
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ mutex_unlock(&hdr->block_mutex);
+ 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 = (size + PAGE_SIZE - 1) / 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;
+
+ mutex_lock(&hdr->block_mutex);
+ blk = (struct snd_emu10k1_memblk *)__snd_util_mem_alloc(hdr, size);
+ if (blk == NULL) {
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ if (synth_alloc_pages(hw, blk)) {
+ __snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ snd_emu10k1_memblk_map(hw, blk);
+ mutex_unlock(&hdr->block_mutex);
+ 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;
+ unsigned long flags;
+
+ mutex_lock(&hdr->block_mutex);
+ spin_lock_irqsave(&emu->memblk_lock, flags);
+ if (blk->mapped_page >= 0)
+ unmap_memblk(emu, blk);
+ spin_unlock_irqrestore(&emu->memblk_lock, flags);
+ synth_free_pages(emu, blk);
+ __snd_util_mem_free(hdr, memblk);
+ mutex_unlock(&hdr->block_mutex);
+ 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;
+ if ((p = blk->mem.list.prev) != &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;
+ if ((p = blk->mem.list.next) != &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;
+}
+
+/*
+ * bzero(blk + offset, size)
+ */
+int snd_emu10k1_synth_bzero(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
+ int offset, int size)
+{
+ int page, nextofs, end_offset, temp, temp1;
+ void *ptr;
+ struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
+
+ 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, 0, temp);
+ offset = nextofs;
+ page++;
+ } while (offset < end_offset);
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_synth_bzero);
+
+/*
+ * copy_from_user(blk + offset, data, size)
+ */
+int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
+ int offset, const char __user *data, int size)
+{
+ int page, nextofs, end_offset, temp, temp1;
+ void *ptr;
+ struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
+
+ 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 && copy_from_user(ptr, data, temp))
+ return -EFAULT;
+ 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..1099f102b
--- /dev/null
+++ b/sound/pci/emu10k1/p16v.c
@@ -0,0 +1,856 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * 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 <fmoraes@nc.rr.com>
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <sound/emu10k1.h>
+#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,
+};
+
+static void snd_p16v_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+ struct snd_emu10k1_pcm *epcm = runtime->private_data;
+
+ kfree(epcm);
+}
+
+/* open_playback callback */
+static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substream, int channel_id)
+{
+ struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+ struct snd_emu10k1_voice *channel = &(emu->p16v_voices[channel_id]);
+ struct snd_emu10k1_pcm *epcm;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+ /* dev_dbg(emu->card->dev, "epcm kcalloc: %p\n", epcm); */
+
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->substream = substream;
+ /*
+ dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n",
+ substream->pcm->device, channel_id);
+ */
+ runtime->private_data = epcm;
+ runtime->private_free = snd_p16v_pcm_free_substream;
+
+ runtime->hw = snd_p16v_playback_hw;
+
+ channel->emu = emu;
+ channel->number = channel_id;
+
+ channel->use=1;
+#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; */
+ channel->epcm = epcm;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+
+ runtime->sync.id32[0] = substream->pcm->card->number;
+ runtime->sync.id32[1] = 'P';
+ runtime->sync.id32[2] = 16;
+ runtime->sync.id32[3] = 'V';
+
+ return 0;
+}
+/* open_capture callback */
+static int snd_p16v_pcm_open_capture_channel(struct snd_pcm_substream *substream, int channel_id)
+{
+ struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+ struct snd_emu10k1_voice *channel = &(emu->p16v_capture_voice);
+ struct snd_emu10k1_pcm *epcm;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+ /* dev_dbg(emu->card->dev, "epcm kcalloc: %p\n", epcm); */
+
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->substream = substream;
+ /*
+ dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n",
+ substream->pcm->device, channel_id);
+ */
+ runtime->private_data = epcm;
+ runtime->private_free = snd_p16v_pcm_free_substream;
+
+ runtime->hw = snd_p16v_capture_hw;
+
+ channel->emu = emu;
+ channel->number = channel_id;
+
+ channel->use=1;
+#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; */
+ channel->epcm = epcm;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+
+ return 0;
+}
+
+
+/* close callback */
+static int snd_p16v_pcm_close_playback(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;
+ emu->p16v_voices[substream->pcm->device - emu->p16v_device_offset].use = 0;
+ /* FIXME: maybe zero others */
+ return 0;
+}
+
+/* close callback */
+static int snd_p16v_pcm_close_capture(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;
+ emu->p16v_capture_voice.use = 0;
+ /* FIXME: maybe zero others */
+ 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);
+}
+
+/* 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);
+ switch (runtime->rate) {
+ case 44100:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x8080);
+ break;
+ case 96000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x4040);
+ break;
+ case 192000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x2020);
+ break;
+ case 48000:
+ default:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x0000);
+ 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, 0x07, channel, 0x0);
+ snd_emu10k1_ptr20_write(emu, 0x08, 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;
+ u32 tmp;
+
+ /*
+ 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));
+ */
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel);
+ switch (runtime->rate) {
+ case 44100:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0800);
+ break;
+ case 96000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0400);
+ break;
+ case 192000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0200);
+ break;
+ case 48000:
+ default:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0000);
+ break;
+ }
+ /* FIXME: Check emu->buffer.size before actually writing to it. */
+ snd_emu10k1_ptr20_write(emu, 0x13, 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<<channel));
+
+ return 0;
+}
+
+static void snd_p16v_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE2) | intrenb;
+ outl(enable, emu->port + INTE2);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_p16v_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int disable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ disable = inl(emu->port + INTE2) & (~intrenb);
+ outl(disable, emu->port + INTE2);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+/* 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;
+ struct snd_emu10k1_pcm *epcm;
+ 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;
+ epcm = runtime->private_data;
+ channel = substream->pcm->device-emu->p16v_device_offset;
+ /* dev_dbg(emu->card->dev, "p16v channel=%d\n", channel); */
+ epcm->running = running;
+ basic |= (0x1<<channel);
+ inte |= (INTE2_PLAYBACK_CH_0_LOOP<<channel);
+ snd_pcm_trigger_done(s, substream);
+ }
+ /* dev_dbg(emu->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;
+ struct snd_emu10k1_pcm *epcm = runtime->private_data;
+ 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<<channel));
+ epcm->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<<channel));
+ snd_p16v_intr_disable(emu, inte);
+ //snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel));
+ epcm->running = 0;
+ 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;
+ struct snd_emu10k1_pcm *epcm = runtime->private_data;
+ snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0;
+ int channel = substream->pcm->device - emu->p16v_device_offset;
+ if (!epcm->running)
+ 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;
+ struct snd_emu10k1_pcm *epcm = runtime->private_data;
+ snd_pcm_uframes_t ptr, ptr1, ptr2 = 0;
+ int channel = 0;
+
+ if (!epcm->running)
+ 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,
+ .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_free(struct snd_emu10k1 *chip)
+{
+ // release the data
+ if (chip->p16v_buffer.area) {
+ snd_dma_free_pages(&chip->p16v_buffer);
+ /*
+ dev_dbg(chip->card->dev, "period lables free: %p\n",
+ &chip->p16v_buffer);
+ */
+ }
+ return 0;
+}
+
+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;
+
+ if ((err = snd_pcm_new(emu->card, "p16v", device, 1, capture, &pcm)) < 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;
+ strcpy(pcm->name, "p16v");
+ emu->pcm_p16v = pcm;
+
+ 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++) {
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&p16v_mixer_controls[i],
+ emu))) < 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..3cdafa311
--- /dev/null
+++ b/sound/pci/emu10k1/p16v.h
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Driver p16v chips
+ * Version: 0.21
+ *
+ * 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
+ * Split from p16v.c
+ *
+ * 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 <fmoraes@nc.rr.com>
+ */
+
+/********************************************************************************************************/
+/* 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<<channel_id) Don't touch high 16bits. */
+ /* Capture (0x100<<channel_id). not tested */
+ /* Start Playback [3:0] (one bit per channel)
+ * Start Capture [11:8] (one bit per channel)
+ * 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 channel.
+ * 1 - I2S channel.
+ * 2 - SRC48 channel.
+ * 3 - SRCMulti_SPDIF channel.
+ * 4 - SRCMulti_I2S channel.
+ * 5 - SPDIF channel.
+ * 6 - fxengine capture.
+ * 7 - AC97 capture.
+ */
+ /* Default 41110000.
+ * Writing 0xffffffff hangs the PC.
+ * Writing 0xffff0000 -> 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..3a6568346
--- /dev/null
+++ b/sound/pci/emu10k1/p17v.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Driver p17v chips
+ * Version: 0.01
+ */
+
+/******************************************************************************/
+/* Audigy2Value Tina (P17V) pointer-offset register set,
+ * accessed through the PTR20 and DATA24 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..c2803000a
--- /dev/null
+++ b/sound/pci/emu10k1/timer.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Lee Revell <rlrevell@joe-job.com>
+ * Clemens Ladisch <clemens@ladisch.de>
+ * Routines for control of EMU10K1 chips
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static int snd_emu10k1_timer_start(struct snd_timer *timer)
+{
+ struct snd_emu10k1 *emu;
+ unsigned long flags;
+ unsigned int delay;
+
+ emu = snd_timer_chip(timer);
+ delay = timer->sticks - 1;
+ if (delay < 5 ) /* minimum time is 5 ticks */
+ delay = 5;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB);
+ outw(delay & TIMER_RATE_MASK, emu->port + TIMER);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_timer_stop(struct snd_timer *timer)
+{
+ struct snd_emu10k1 *emu;
+ unsigned long flags;
+
+ emu = snd_timer_chip(timer);
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_timer_precise_resolution(struct snd_timer *timer,
+ unsigned long *num, unsigned long *den)
+{
+ *num = 1;
+ *den = 48000;
+ return 0;
+}
+
+static const struct snd_timer_hardware snd_emu10k1_timer_hw = {
+ .flags = SNDRV_TIMER_HW_AUTO,
+ .resolution = 20833, /* 1 sample @ 48KHZ = 20.833...us */
+ .ticks = 1024,
+ .start = snd_emu10k1_timer_start,
+ .stop = snd_emu10k1_timer_stop,
+ .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;
+ if ((err = snd_timer_new(emu->card, "EMU10K1", &tid, &timer)) >= 0) {
+ strcpy(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..7fd235345
--- /dev/null
+++ b/sound/pci/emu10k1/tina2.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Driver tina2 chips
+ * Version: 0.1
+ */
+
+/********************************************************************************************************/
+/* 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..cbeb84434
--- /dev/null
+++ b/sound/pci/emu10k1/voice.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Creative Labs, Inc.
+ * Lee Revell <rlrevell@joe-job.com>
+ * Routines for control of EMU10K1 chips - voice manager
+ *
+ * Rewrote voice allocator for multichannel support - rlrevell 12/2004
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ */
+
+#include <linux/time.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+/* 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. For multichannel voice allocation we ensure than the block of
+ * voices does not cross the 32 voice boundary. This simplifies the
+ * multichannel support and ensures we can use a single write to the
+ * (set|clear)_loop_stop registers. Otherwise (for example) the voices would
+ * get out of sync when pausing/resuming a stream.
+ * --rlrevell
+ */
+
+static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
+ struct snd_emu10k1_voice **rvoice)
+{
+ struct snd_emu10k1_voice *voice;
+ int i, j, k, first_voice, last_voice, skip;
+
+ *rvoice = NULL;
+ first_voice = last_voice = 0;
+ for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
+ /*
+ dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
+ i, j, emu->next_free_voice);
+ */
+ i %= NUM_G;
+
+ /* stereo voices must be even/odd */
+ if ((number == 2) && (i % 2)) {
+ i++;
+ continue;
+ }
+
+ skip = 0;
+ for (k = 0; k < number; k++) {
+ voice = &emu->voices[(i+k) % NUM_G];
+ if (voice->use) {
+ skip = 1;
+ break;
+ }
+ }
+ if (!skip) {
+ /* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */
+ first_voice = i;
+ last_voice = (i + number) % NUM_G;
+ emu->next_free_voice = last_voice;
+ break;
+ }
+ }
+
+ if (first_voice == last_voice)
+ return -ENOMEM;
+
+ for (i = 0; i < number; i++) {
+ voice = &emu->voices[(first_voice + i) % NUM_G];
+ /*
+ dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
+ voice->number, idx-first_voice+1, number);
+ */
+ voice->use = 1;
+ switch (type) {
+ case EMU10K1_PCM:
+ voice->pcm = 1;
+ break;
+ case EMU10K1_SYNTH:
+ voice->synth = 1;
+ break;
+ case EMU10K1_MIDI:
+ voice->midi = 1;
+ break;
+ case EMU10K1_EFX:
+ voice->efx = 1;
+ break;
+ }
+ }
+ *rvoice = &emu->voices[first_voice];
+ return 0;
+}
+
+int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number,
+ struct snd_emu10k1_voice **rvoice)
+{
+ unsigned long flags;
+ int result;
+
+ if (snd_BUG_ON(!rvoice))
+ return -EINVAL;
+ if (snd_BUG_ON(!number))
+ return -EINVAL;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (;;) {
+ result = voice_alloc(emu, type, number, rvoice);
+ if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
+ break;
+
+ /* free a voice from synth */
+ if (emu->get_synth_voice) {
+ result = emu->get_synth_voice(emu);
+ if (result >= 0) {
+ struct snd_emu10k1_voice *pvoice = &emu->voices[result];
+ pvoice->interrupt = NULL;
+ pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
+ pvoice->epcm = NULL;
+ }
+ }
+ if (result < 0)
+ break;
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+ return result;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
+
+int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_voice *pvoice)
+{
+ unsigned long flags;
+
+ if (snd_BUG_ON(!pvoice))
+ return -EINVAL;
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ pvoice->interrupt = NULL;
+ pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
+ pvoice->epcm = NULL;
+ snd_emu10k1_voice_init(emu, pvoice->number);
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+ 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..d9acef082
--- /dev/null
+++ b/sound/pci/ens1370.c
@@ -0,0 +1,2435 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Ensoniq ES1370/ES1371 AudioPCI soundcard
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ * Thomas Sailer <sailer@ife.ee.ethz.ch>
+ */
+
+/* Power-Management-Code ( CONFIG_PM )
+ * for ens1371 only ( FIXME )
+ * derived from cs4281.c, atiixp.c and via82xx.c
+ * using http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/
+ * by Kurt J. Bosch
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#ifdef CHIP1371
+#include <sound/ac97_codec.h>
+#else
+#include <sound/ak4531_codec.h>
+#endif
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+
+#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 <perex@perex.cz>, Thomas Sailer <sailer@ife.ee.ethz.ch>");
+MODULE_LICENSE("GPL");
+#ifdef CHIP1370
+MODULE_DESCRIPTION("Ensoniq AudioPCI ES1370");
+MODULE_SUPPORTED_DEVICE("{{Ensoniq,AudioPCI-97 ES1370},"
+ "{Creative Labs,SB PCI64/128 (ES1370)}}");
+#endif
+#ifdef CHIP1371
+MODULE_DESCRIPTION("Ensoniq/Creative AudioPCI ES1371+");
+MODULE_SUPPORTED_DEVICE("{{Ensoniq,AudioPCI ES1371/73},"
+ "{Ensoniq,AudioPCI ES1373},"
+ "{Creative Labs,Ectiva EV1938},"
+ "{Creative Labs,SB PCI64/128 (ES1371/73)},"
+ "{Creative Labs,Vibra PCI128},"
+ "{Ectiva,EV1938}}");
+#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), 0, }, /* ES1370 */
+#endif
+#ifdef CHIP1371
+ { PCI_VDEVICE(ENSONIQ, 0x1371), 0, }, /* ES1371 */
+ { PCI_VDEVICE(ENSONIQ, 0x5880), 0, }, /* ES1373 - CT5880 */
+ { PCI_VDEVICE(ECTIVA, 0x8938), 0, }, /* Ectiva EV1938 */
+#endif
+ { 0, }
+};
+
+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;
+ 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_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));
+ mutex_unlock(&ensoniq->src_mutex);
+ return;
+ }
+ }
+ mutex_unlock(&ensoniq->src_mutex);
+ 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++) {
+ if ((x = inl(ES_REG(ensoniq, 1371_CODEC))) & 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ensoniq->src_mutex);
+}
+
+static void snd_es1371_dac1_rate(struct ensoniq * ensoniq, unsigned int rate)
+{
+ unsigned int freq, r;
+
+ mutex_lock(&ensoniq->src_mutex);
+ freq = ((rate << 15) + 1500) / 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));
+ mutex_unlock(&ensoniq->src_mutex);
+}
+
+static void snd_es1371_dac2_rate(struct ensoniq * ensoniq, unsigned int rate)
+{
+ unsigned int freq, r;
+
+ mutex_lock(&ensoniq->src_mutex);
+ freq = ((rate << 15) + 1500) / 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));
+ mutex_unlock(&ensoniq->src_mutex);
+}
+
+#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;
+ }
+ spin_lock(&ensoniq->reg_lock);
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+ ensoniq->sctrl |= what;
+ else
+ ensoniq->sctrl &= ~what;
+ outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+ spin_unlock(&ensoniq->reg_lock);
+ 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);
+ }
+ }
+ spin_lock(&ensoniq->reg_lock);
+ if (cmd == SNDRV_PCM_TRIGGER_START)
+ ensoniq->ctrl |= what;
+ else
+ ensoniq->ctrl &= ~what;
+ outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+ spin_unlock(&ensoniq->reg_lock);
+ 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;
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+#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;
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+#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;
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+#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;
+
+ spin_lock(&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)));
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ } else {
+ ptr = 0;
+ }
+ spin_unlock(&ensoniq->reg_lock);
+ return ptr;
+}
+
+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;
+
+ spin_lock(&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)));
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ } else {
+ ptr = 0;
+ }
+ spin_unlock(&ensoniq->reg_lock);
+ return ptr;
+}
+
+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;
+
+ spin_lock(&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)));
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ } else {
+ ptr = 0;
+ }
+ spin_unlock(&ensoniq->reg_lock);
+ return ptr;
+}
+
+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);
+ spin_lock_irq(&ensoniq->reg_lock);
+ if (ensoniq->spdif && ensoniq->playback2_substream == NULL)
+ ensoniq->spdif_stream = ensoniq->spdif_default;
+ spin_unlock_irq(&ensoniq->reg_lock);
+#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);
+ spin_lock_irq(&ensoniq->reg_lock);
+ if (ensoniq->spdif && ensoniq->playback1_substream == NULL)
+ ensoniq->spdif_stream = ensoniq->spdif_default;
+ spin_unlock_irq(&ensoniq->reg_lock);
+#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;
+ spin_lock_irq(&ensoniq->reg_lock);
+#ifdef CHIP1370
+ ensoniq->u.es1370.pclkdiv_lock &= ~ES_MODE_PLAY2;
+#endif
+ ensoniq->mode &= ~ES_MODE_PLAY2;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+ spin_lock_irq(&ensoniq->reg_lock);
+#ifdef CHIP1370
+ ensoniq->u.es1370.pclkdiv_lock &= ~ES_MODE_CAPTURE;
+#endif
+ ensoniq->mode &= ~ES_MODE_CAPTURE;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+ strcpy(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;
+ strcpy(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);
+ spin_lock_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;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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);
+ spin_lock_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;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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);
+
+ spin_lock_irq(&ensoniq->reg_lock);
+ ucontrol->value.integer.value[0] = ensoniq->ctrl & ES_1373_SPDIF_THRU ? 1 : 0;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+
+ spin_lock_irq(&ensoniq->reg_lock);
+ if (ensoniq->ctrl & ES_1371_GPIO_OUT(4))
+ val = 1;
+ ucontrol->value.integer.value[0] = val;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+
+ spin_lock_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));
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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,
+ };
+
+ if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 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;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &ensoniq->u.es1371.ac97)) < 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;
+
+ spin_lock_irq(&ensoniq->reg_lock);
+ ucontrol->value.integer.value[0] = ensoniq->ctrl & mask ? 1 : 0;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+ spin_lock_irq(&ensoniq->reg_lock);
+ change = (ensoniq->ctrl & mask) != nval;
+ ensoniq->ctrl &= ~mask;
+ ensoniq->ctrl |= nval;
+ outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+ spin_unlock_irq(&ensoniq->reg_lock);
+ 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;
+ if ((err = snd_ak4531_mixer(card, &ak4531, &ensoniq->u.es1370.ak4531)) < 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",
+ ensoniq->ctrl & ES_JYSTK_EN ? "on" : "off");
+#ifdef CHIP1370
+ snd_iprintf(buffer, "MIC +5V bias : %s\n",
+ ensoniq->ctrl & ES_1370_XCTL1 ? "on" : "off");
+ snd_iprintf(buffer, "Line In to AOUT : %s\n",
+ ensoniq->ctrl & ES_1370_XCTL0 ? "on" : "off");
+#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 int snd_ensoniq_free(struct ensoniq *ensoniq)
+{
+ snd_ensoniq_free_gameport(ensoniq);
+ if (ensoniq->irq < 0)
+ goto __hw_end;
+#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
+ pci_set_power_state(ensoniq->pci, PCI_D3hot);
+ __hw_end:
+#ifdef CHIP1370
+ if (ensoniq->dma_bug.area)
+ snd_dma_free_pages(&ensoniq->dma_bug);
+#endif
+ if (ensoniq->irq >= 0)
+ free_irq(ensoniq->irq, ensoniq);
+ pci_release_regions(ensoniq->pci);
+ pci_disable_device(ensoniq->pci);
+ kfree(ensoniq);
+ return 0;
+}
+
+static int snd_ensoniq_dev_free(struct snd_device *device)
+{
+ struct ensoniq *ensoniq = device->device_data;
+ return snd_ensoniq_free(ensoniq);
+}
+
+#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));
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(snd_ensoniq_pm, snd_ensoniq_suspend, snd_ensoniq_resume);
+#define SND_ENSONIQ_PM_OPS &snd_ensoniq_pm
+#else
+#define SND_ENSONIQ_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static int snd_ensoniq_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct ensoniq **rensoniq)
+{
+ struct ensoniq *ensoniq;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_ensoniq_dev_free,
+ };
+
+ *rensoniq = NULL;
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ ensoniq = kzalloc(sizeof(*ensoniq), GFP_KERNEL);
+ if (ensoniq == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ spin_lock_init(&ensoniq->reg_lock);
+ mutex_init(&ensoniq->src_mutex);
+ ensoniq->card = card;
+ ensoniq->pci = pci;
+ ensoniq->irq = -1;
+ if ((err = pci_request_regions(pci, "Ensoniq AudioPCI")) < 0) {
+ kfree(ensoniq);
+ pci_disable_device(pci);
+ return err;
+ }
+ ensoniq->port = pci_resource_start(pci, 0);
+ if (request_irq(pci->irq, snd_audiopci_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, ensoniq)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_ensoniq_free(ensoniq);
+ return -EBUSY;
+ }
+ ensoniq->irq = pci->irq;
+ card->sync_irq = ensoniq->irq;
+#ifdef CHIP1370
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ 16, &ensoniq->dma_bug) < 0) {
+ dev_err(card->dev, "unable to allocate space for phantom area - dma_bug\n");
+ snd_ensoniq_free(ensoniq);
+ return -EBUSY;
+ }
+#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
+
+ snd_ensoniq_chip_init(ensoniq);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ensoniq, &ops)) < 0) {
+ snd_ensoniq_free(ensoniq);
+ return err;
+ }
+
+ snd_ensoniq_proc_init(ensoniq);
+
+ *rensoniq = 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 */
+ spin_lock(&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);
+ }
+ spin_unlock(&ensoniq->reg_lock);
+
+ /* do Tx at second */
+ spin_lock(&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));
+ }
+ }
+ spin_unlock(&ensoniq->reg_lock);
+}
+
+static int snd_ensoniq_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+ spin_lock_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));
+ }
+ spin_unlock_irq(&ensoniq->reg_lock);
+ return 0;
+}
+
+static int snd_ensoniq_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+ spin_lock_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;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ return 0;
+}
+
+static int snd_ensoniq_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+ spin_lock_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));
+ }
+ spin_unlock_irq(&ensoniq->reg_lock);
+ return 0;
+}
+
+static int snd_ensoniq_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+ spin_lock_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;
+ spin_unlock_irq(&ensoniq->reg_lock);
+ return 0;
+}
+
+static void snd_ensoniq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct ensoniq *ensoniq = substream->rmidi->private_data;
+ int idx;
+
+ spin_lock_irqsave(&ensoniq->reg_lock, flags);
+ 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));
+ }
+ }
+ spin_unlock_irqrestore(&ensoniq->reg_lock, flags);
+}
+
+static void snd_ensoniq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct ensoniq *ensoniq = substream->rmidi->private_data;
+ unsigned char byte;
+
+ spin_lock_irqsave(&ensoniq->reg_lock, flags);
+ 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));
+ }
+ }
+ spin_unlock_irqrestore(&ensoniq->reg_lock, flags);
+}
+
+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;
+
+ if ((err = snd_rawmidi_new(ensoniq->card, "ES1370/1", device, 1, 1, &rmidi)) < 0)
+ return err;
+ strcpy(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;
+
+ spin_lock(&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));
+ spin_unlock(&ensoniq->reg_lock);
+
+ 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if ((err = snd_ensoniq_create(card, pci, &ensoniq)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = ensoniq;
+
+#ifdef CHIP1370
+ if ((err = snd_ensoniq_1370_mixer(ensoniq)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#endif
+#ifdef CHIP1371
+ if ((err = snd_ensoniq_1371_mixer(ensoniq, spdif[dev], lineio[dev])) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#endif
+ if ((err = snd_ensoniq_pcm(ensoniq, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_ensoniq_pcm2(ensoniq, 1)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_ensoniq_midi(ensoniq, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_ensoniq_create_gameport(ensoniq, dev);
+
+ strcpy(card->driver, DRIVER_NAME);
+
+ strcpy(card->shortname, "Ensoniq AudioPCI");
+ sprintf(card->longname, "%s %s at 0x%lx, irq %i",
+ card->shortname,
+ card->driver,
+ ensoniq->port,
+ ensoniq->irq);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_audiopci_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver ens137x_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_audiopci_ids,
+ .probe = snd_audiopci_probe,
+ .remove = snd_audiopci_remove,
+ .driver = {
+ .pm = SND_ENSONIQ_PM_OPS,
+ },
+};
+
+module_pci_driver(ens137x_driver);
diff --git a/sound/pci/ens1371.c b/sound/pci/ens1371.c
new file mode 100644
index 000000000..ca0da0ab4
--- /dev/null
+++ b/sound/pci/ens1371.c
@@ -0,0 +1,2 @@
+#define CHIP1371
+#include "ens1370.c"
diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c
new file mode 100644
index 000000000..09704a78d
--- /dev/null
+++ b/sound/pci/es1938.c
@@ -0,0 +1,1848 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard
+ * Copyright (c) by Jaromir Koutek <miri@punknet.cz>,
+ * Jaroslav Kysela <perex@perex.cz>,
+ * Thomas Sailer <sailer@ife.ee.ethz.ch>,
+ * Abramo Bagnara <abramo@alsa-project.org>,
+ * Markus Gruber <gruber@eikon.tum.de>
+ *
+ * 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 <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+MODULE_AUTHOR("Jaromir Koutek <miri@punknet.cz>");
+MODULE_DESCRIPTION("ESS Solo-1");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,ES1938},"
+ "{ESS,ES1946},"
+ "{ESS,ES1969},"
+ "{TerraTec,128i PCI}}");
+
+#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
+#ifdef CONFIG_PM_SLEEP
+ unsigned char saved_regs[SAVED_REG_SIZE];
+#endif
+};
+
+static irqreturn_t snd_es1938_interrupt(int irq, void *dev_id);
+
+static const struct pci_device_id snd_es1938_ids[] = {
+ { PCI_VDEVICE(ESS, 0x1969), 0, }, /* Solo-1 */
+ { 0, }
+};
+
+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)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ outb(reg, SLSB_REG(chip, MIXERADDR));
+ outb(val, SLSB_REG(chip, MIXERDATA));
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ 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;
+ unsigned long flags;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ outb(reg, SLSB_REG(chip, MIXERADDR));
+ data = inb(SLSB_REG(chip, MIXERDATA));
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ 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 long flags;
+ unsigned char old, new, oval;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ 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);
+ }
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ 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++) {
+ if (!(v = inb(SLSB_REG(chip, READSTATUS)) & 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--)
+ if ((v = inb(SLSB_REG(chip, STATUS))) & 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)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1938_write_cmd(chip, reg);
+ snd_es1938_write_cmd(chip, val);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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;
+ unsigned long flags;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1938_write_cmd(chip, ESS_CMD_READREG);
+ snd_es1938_write_cmd(chip, reg);
+ val = snd_es1938_get_byte(chip);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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 long flags;
+ unsigned char old, new, oval;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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,
+ void __user *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_user(dst, runtime->dma_area + pos + 1, count))
+ return -EFAULT;
+ } else {
+ if (copy_to_user(dst, runtime->dma_area + pos + 1, count - 1))
+ return -EFAULT;
+ if (put_user(runtime->dma_area[0],
+ ((unsigned char __user *)dst) + count - 1))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int snd_es1938_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *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) {
+ memcpy(dst, runtime->dma_area + pos + 1, count);
+ } else {
+ memcpy(dst, runtime->dma_area + pos + 1, count - 1);
+ runtime->dma_area[0] = *((unsigned char *)dst + count - 1);
+ }
+ 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_user = snd_es1938_capture_copy,
+ .copy_kernel = snd_es1938_capture_copy_kernel,
+};
+
+static int snd_es1938_new_pcm(struct es1938 *chip, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ if ((err = snd_pcm_new(chip->card, "es-1938-1946", device, 2, 1, &pcm)) < 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;
+ strcpy(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));
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(es1938_pm, es1938_suspend, es1938_resume);
+#define ES1938_PM_OPS &es1938_pm
+#else
+#define ES1938_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+#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 int snd_es1938_free(struct es1938 *chip)
+{
+ /* 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);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_es1938_dev_free(struct snd_device *device)
+{
+ struct es1938 *chip = device->device_data;
+ return snd_es1938_free(chip);
+}
+
+static int snd_es1938_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct es1938 **rchip)
+{
+ struct es1938 *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_es1938_dev_free,
+ };
+
+ *rchip = NULL;
+
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 24 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(24)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(24)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 24bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ spin_lock_init(&chip->reg_lock);
+ spin_lock_init(&chip->mixer_lock);
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+ if ((err = pci_request_regions(pci, "ESS Solo-1")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ 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);
+ 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);
+ snd_es1938_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ 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);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_es1938_free(chip);
+ return err;
+ }
+
+ *rchip = 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;
+
+ strcpy(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);
+ 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;
+ }
+ if ((err = snd_ctl_add(card, kctl)) < 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+ for (idx = 0; idx < 5; idx++) {
+ if (pci_resource_start(pci, idx) == 0 ||
+ !(pci_resource_flags(pci, idx) & IORESOURCE_IO)) {
+ snd_card_free(card);
+ return -ENODEV;
+ }
+ }
+ if ((err = snd_es1938_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ strcpy(card->driver, "ES1938");
+ strcpy(card->shortname, "ESS ES1938 (Solo-1)");
+ sprintf(card->longname, "%s rev %i, irq %i",
+ card->shortname,
+ chip->revision,
+ chip->irq);
+
+ if ((err = snd_es1938_new_pcm(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_es1938_mixer(chip)) < 0) {
+ snd_card_free(card);
+ 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 {
+ if ((err = snd_opl3_timer_new(opl3, 0, 1)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+ snd_card_free(card);
+ 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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_es1938_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver es1938_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_es1938_ids,
+ .probe = snd_es1938_probe,
+ .remove = snd_es1938_remove,
+ .driver = {
+ .pm = ES1938_PM_OPS,
+ },
+};
+
+module_pci_driver(es1938_driver);
diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c
new file mode 100644
index 000000000..34332d008
--- /dev/null
+++ b/sound/pci/es1968.c
@@ -0,0 +1,2914 @@
+// 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 <MatzeBraun@gmx.de>.
+ * Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/input.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#ifdef CONFIG_SND_ES1968_RADIO
+#include <media/drv-intf/tea575x.h>
+#endif
+
+#define CARD_NAME "ESS Maestro1/2"
+#define DRIVER_NAME "ES1968"
+
+MODULE_DESCRIPTION("ESS Maestro");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,Maestro 2e},"
+ "{ESS,Maestro 2},"
+ "{ESS,Maestro 1},"
+ "{TerraTec,DMX}}");
+
+#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;
+
+#ifdef CONFIG_PM_SLEEP
+ u16 wc_map[4];
+#endif
+};
+
+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;
+
+#ifdef CONFIG_PM_SLEEP
+ u16 apu_map[NR_APUS][NR_APU_REGS];
+#endif
+
+#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 */
+ { 0x1285, 0x0100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, TYPE_MAESTRO },
+ /* Maestro 2 */
+ { 0x125d, 0x1968, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, TYPE_MAESTRO2 },
+ /* Maestro 2E */
+ { 0x125d, 0x1978, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, TYPE_MAESTRO2E },
+ { 0, }
+};
+
+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)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ __maestro_write(chip, reg, data);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/* 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)
+{
+ unsigned long flags;
+ u16 result;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ result = __maestro_read(chip, reg);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return result;
+}
+
+/* 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;
+#ifdef CONFIG_PM_SLEEP
+ chip->apu_map[channel][reg] = data;
+#endif
+ 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)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ __apu_set_register(chip, channel, reg, data);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+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)
+{
+ unsigned long flags;
+ u16 v;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ v = __apu_get_register(chip, channel, reg);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return v;
+}
+
+#if 0 /* ASSP is not supported */
+
+static void assp_set_register(struct es1968 *chip, u32 reg, u32 value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ outl(reg, chip->io_port + ASSP_INDEX);
+ outl(value, chip->io_port + ASSP_DATA);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static u32 assp_get_register(struct es1968 *chip, u32 reg)
+{
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ outl(reg, chip->io_port + ASSP_INDEX);
+ value = inl(chip->io_port + ASSP_DATA);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ return value;
+}
+
+#endif
+
+static void wave_set_register(struct es1968 *chip, u16 reg, u16 value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ outw(reg, chip->io_port + WC_INDEX);
+ outw(value, chip->io_port + WC_DATA);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static u16 wave_get_register(struct es1968 *chip, u16 reg)
+{
+ unsigned long flags;
+ u16 value;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ outw(reg, chip->io_port + WC_INDEX);
+ value = inw(chip->io_port + WC_DATA);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ return value;
+}
+
+/* *******************
+ * 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)
+{
+ spin_lock(&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]);
+ }
+ }
+ spin_unlock(&chip->reg_lock);
+}
+
+static void snd_es1968_pcm_stop(struct es1968 *chip, struct esschan *es)
+{
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+}
+
+/* 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);
+
+#ifdef CONFIG_PM_SLEEP
+ es->wc_map[channel] = tmpval;
+#endif
+}
+
+
+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;
+ unsigned long flags;
+ 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);
+ }
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ /* 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ 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);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ /* 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*******************
+ * 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;
+
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->substream_lock);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->memory_mutex);
+ 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);
+ mutex_lock(&chip->memory_mutex);
+ list_for_each_entry(buf, &chip->buf_list, list) {
+ if (buf->empty && buf->buf.bytes >= size)
+ goto __found;
+ }
+ mutex_unlock(&chip->memory_mutex);
+ return NULL;
+
+__found:
+ if (buf->buf.bytes > size) {
+ struct esm_memory *chunk = kmalloc(sizeof(*chunk), GFP_KERNEL);
+ if (chunk == NULL) {
+ mutex_unlock(&chip->memory_mutex);
+ 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;
+ mutex_unlock(&chip->memory_mutex);
+ return buf;
+}
+
+/* free a memory chunk */
+static void snd_es1968_free_memory(struct es1968 *chip, struct esm_memory *buf)
+{
+ struct esm_memory *chunk;
+
+ mutex_lock(&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);
+ }
+ }
+ mutex_unlock(&chip->memory_mutex);
+}
+
+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(sizeof(*chunk), GFP_KERNEL);
+ 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(sizeof(*es), GFP_KERNEL);
+ 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);
+
+ spin_lock_irq(&chip->substream_lock);
+ list_add(&es->list, &chip->substream_list);
+ spin_unlock_irq(&chip->substream_lock);
+
+ 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 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(sizeof(*es), GFP_KERNEL);
+ 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 */
+ if ((es->mixbuf = snd_es1968_new_memory(chip, ESM_MIXBUF_SIZE)) == NULL) {
+ 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 */
+ snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
+
+ spin_lock_irq(&chip->substream_lock);
+ list_add(&es->list, &chip->substream_list);
+ spin_unlock_irq(&chip->substream_lock);
+
+ 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;
+ spin_lock_irq(&chip->substream_lock);
+ list_del(&es->list);
+ spin_unlock_irq(&chip->substream_lock);
+ 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;
+ spin_lock_irq(&chip->substream_lock);
+ list_del(&es->list);
+ spin_unlock_irq(&chip->substream_lock);
+ 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) */
+ if ((apu = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_PLAY)) < 0) {
+ dev_err(chip->card->dev, "Hmm, cannot find empty APU pair!?\n");
+ return;
+ }
+ if ((memory = snd_es1968_new_memory(chip, CLOCK_MEASURE_BUFSIZE)) == NULL) {
+ 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);
+ spin_lock_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 */
+ spin_unlock_irq(&chip->reg_lock);
+
+ snd_es1968_apu_set_freq(chip, apu, ((unsigned int)48000 << 16) / chip->clock); /* 48000 Hz */
+
+ chip->in_measurement = 1;
+ chip->measure_apu = apu;
+ spin_lock_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();
+ spin_unlock_irq(&chip->reg_lock);
+ msleep(50);
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+
+ /* 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 */
+ if ((err = snd_es1968_init_dmabuf(chip)) < 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);
+
+ if ((err = snd_pcm_new(chip->card, "ESS Maestro", device,
+ chip->playback_streams,
+ chip->capture_streams, &pcm)) < 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;
+
+ strcpy(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;
+
+ if (!(event = inb(chip->io_port + 0x1A)))
+ 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;
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&chip->substream_lock);
+ 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;
+#ifndef CONFIG_SND_ES1968_INPUT
+ struct snd_ctl_elem_id elem_id;
+#endif
+ int err;
+ static const struct snd_ac97_bus_ops ops = {
+ .write = snd_es1968_ac97_write,
+ .read = snd_es1968_ac97_read,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+ return err;
+ pbus->no_vra = 1; /* ES1968 doesn't need VRA */
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = chip;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0)
+ return err;
+
+#ifndef CONFIG_SND_ES1968_INPUT
+ /* attach master switch / volumes for h/w volume control */
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(elem_id.name, "Master Playback Switch");
+ chip->master_switch = snd_ctl_find_id(chip->card, &elem_id);
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(elem_id.name, "Master Playback Volume");
+ chip->master_volume = snd_ctl_find_id(chip->card, &elem_id);
+#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);
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(es1968_pm, es1968_suspend, es1968_resume);
+#define ES1968_PM_OPS &es1968_pm
+#else
+#define ES1968_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+#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 = request_region(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");
+ release_and_free_resource(r);
+ 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_set_port_data(gp, r);
+
+ gameport_register_port(gp);
+
+ return 0;
+}
+
+static void snd_es1968_free_gameport(struct es1968 *chip)
+{
+ if (chip->gameport) {
+ struct resource *r = gameport_get_port_data(chip->gameport);
+
+ gameport_unregister_port(chip->gameport);
+ chip->gameport = NULL;
+
+ release_and_free_resource(r);
+ }
+}
+#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 = input_allocate_device();
+ 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) {
+ input_free_device(input_dev);
+ 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 int snd_es1968_free(struct es1968 *chip)
+{
+ cancel_work_sync(&chip->hwvol_work);
+#ifdef CONFIG_SND_ES1968_INPUT
+ if (chip->input_dev)
+ input_unregister_device(chip->input_dev);
+#endif
+
+ 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
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ snd_es1968_free_gameport(chip);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_es1968_dev_free(struct snd_device *device)
+{
+ struct es1968 *chip = device->device_data;
+ return snd_es1968_free(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_ret)
+{
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_es1968_dev_free,
+ };
+ struct es1968 *chip;
+ int i, err;
+
+ *chip_ret = NULL;
+
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 28 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(28)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(28)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 28bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (! chip) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ /* 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;
+
+ if ((err = pci_request_regions(pci, "ESS Maestro")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->io_port = pci_resource_start(pci, 0);
+ if (request_irq(pci->irq, snd_es1968_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_es1968_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ /* 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);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_es1968_free(chip);
+ return err;
+ }
+
+#ifdef CONFIG_SND_ES1968_RADIO
+ /* don't play with GPIOs on laptops */
+ if (chip->pci->subsystem_vendor != 0x125d)
+ goto no_radio;
+ err = v4l2_device_register(&pci->dev, &chip->v4l2_dev);
+ if (err < 0) {
+ snd_es1968_free(chip);
+ 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);
+ strlcpy(chip->tea.card, get_tea575x_gpio(chip)->name,
+ sizeof(chip->tea.card));
+ break;
+ }
+ }
+no_radio:
+#endif
+
+ *chip_ret = chip;
+
+ 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if (total_bufsize[dev] < 128)
+ total_bufsize[dev] = 128;
+ if (total_bufsize[dev] > 4096)
+ total_bufsize[dev] = 4096;
+ if ((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],
+ &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ switch (chip->type) {
+ case TYPE_MAESTRO2E:
+ strcpy(card->driver, "ES1978");
+ strcpy(card->shortname, "ESS ES1978 (Maestro 2E)");
+ break;
+ case TYPE_MAESTRO2:
+ strcpy(card->driver, "ES1968");
+ strcpy(card->shortname, "ESS ES1968 (Maestro 2)");
+ break;
+ case TYPE_MAESTRO:
+ strcpy(card->driver, "ESM1");
+ strcpy(card->shortname, "ESS Maestro 1");
+ break;
+ }
+
+ if ((err = snd_es1968_pcm(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_es1968_mixer(chip)) < 0) {
+ snd_card_free(card);
+ 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]) {
+ if ((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)) < 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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_es1968_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver es1968_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_es1968_ids,
+ .probe = snd_es1968_probe,
+ .remove = snd_es1968_remove,
+ .driver = {
+ .pm = ES1968_PM_OPS,
+ },
+};
+
+module_pci_driver(es1968_driver);
diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c
new file mode 100644
index 000000000..0a95032fd
--- /dev/null
+++ b/sound/pci/fm801.c
@@ -0,0 +1,1462 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The driver for the ForteMedia FM801 based soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+#ifdef CONFIG_SND_FM801_TEA575X_BOOL
+#include <media/drv-intf/tea575x.h>
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ForteMedia FM801");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ForteMedia,FM801},"
+ "{Genius,SoundMaker Live 5.1}}");
+
+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
+
+#ifdef CONFIG_PM_SLEEP
+ u16 saved_regs[0x20];
+#endif
+};
+
+/*
+ * 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[] = {
+ { 0x1319, 0x0801, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0, }, /* FM801 */
+ { 0x5213, 0x0510, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0, }, /* Gallant Odyssey Sound 4 */
+ { 0, }
+};
+
+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 long flags;
+ unsigned short old, new;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ old = fm801_ioread16(chip, reg);
+ new = (old & ~mask) | value;
+ change = old != new;
+ if (change)
+ fm801_iowrite16(chip, reg, new);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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);
+
+ spin_lock(&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:
+ spin_unlock(&chip->reg_lock);
+ snd_BUG();
+ return -EINVAL;
+ }
+ fm801_writew(chip, PLY_CTRL, chip->ply_ctrl);
+ spin_unlock(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_fm801_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct fm801 *chip = snd_pcm_substream_chip(substream);
+
+ spin_lock(&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:
+ spin_unlock(&chip->reg_lock);
+ snd_BUG();
+ return -EINVAL;
+ }
+ fm801_writew(chip, CAP_CTRL, chip->cap_ctrl);
+ spin_unlock(&chip->reg_lock);
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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) {
+ spin_lock(&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);
+ spin_unlock(&chip->reg_lock);
+ snd_pcm_period_elapsed(chip->playback_substream);
+ }
+ if (chip->pcm && (status & FM801_IRQ_CAPTURE) && chip->capture_substream) {
+ spin_lock(&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);
+ spin_unlock(&chip->reg_lock);
+ 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);
+ }
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 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);
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 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;
+
+ if ((err = snd_pcm_new(chip->card, "FM801", device, 1, 1, &pcm)) < 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;
+ strcpy(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;
+
+ spin_lock_irq(&chip->reg_lock);
+ value[0] = (fm801_ioread16(chip, reg) >> shift_left) & mask;
+ value[1] = (fm801_ioread16(chip, reg) >> shift_right) & mask;
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ if ((val = ucontrol->value.enumerated.item[0]) > 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 void snd_fm801_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+ struct fm801 *chip = bus->private_data;
+ chip->ac97_bus = NULL;
+}
+
+static void snd_fm801_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+ struct fm801 *chip = ac97->private_data;
+ if (ac97->num == 0) {
+ chip->ac97 = NULL;
+ } else {
+ chip->ac97_sec = NULL;
+ }
+}
+
+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,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+ return err;
+ chip->ac97_bus->private_free = snd_fm801_mixer_free_ac97_bus;
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = chip;
+ ac97.private_free = snd_fm801_mixer_free_ac97;
+ if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+ return err;
+ if (chip->secondary) {
+ ac97.num = 1;
+ ac97.addr = chip->secondary_addr;
+ if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97_sec)) < 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 int snd_fm801_free(struct fm801 *chip)
+{
+ unsigned short cmdw;
+
+ if (chip->irq < 0)
+ goto __end_hw;
+
+ /* interrupt setup - mask everything */
+ cmdw = fm801_readw(chip, IRQ_MASK);
+ cmdw |= 0x00c3;
+ fm801_writew(chip, IRQ_MASK, cmdw);
+
+ devm_free_irq(chip->dev, chip->irq, chip);
+
+ __end_hw:
+#ifdef CONFIG_SND_FM801_TEA575X_BOOL
+ if (!(chip->tea575x_tuner & TUNER_DISABLED)) {
+ snd_tea575x_exit(&chip->tea);
+ v4l2_device_unregister(&chip->v4l2_dev);
+ }
+#endif
+ return 0;
+}
+
+static int snd_fm801_dev_free(struct snd_device *device)
+{
+ struct fm801 *chip = device->device_data;
+ return snd_fm801_free(chip);
+}
+
+static int snd_fm801_create(struct snd_card *card,
+ struct pci_dev *pci,
+ int tea575x_tuner,
+ int radio_nr,
+ struct fm801 **rchip)
+{
+ struct fm801 *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_fm801_dev_free,
+ };
+
+ *rchip = NULL;
+ if ((err = pcim_enable_device(pci)) < 0)
+ return err;
+ chip = devm_kzalloc(&pci->dev, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+ spin_lock_init(&chip->reg_lock);
+ chip->card = card;
+ chip->dev = &pci->dev;
+ chip->irq = -1;
+ chip->tea575x_tuner = tea575x_tuner;
+ if ((err = pci_request_regions(pci, "FM801")) < 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);
+ snd_fm801_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ pci_set_master(pci);
+ }
+
+ snd_fm801_chip_init(chip);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_fm801_free(chip);
+ return err;
+ }
+
+#ifdef CONFIG_SND_FM801_TEA575X_BOOL
+ err = v4l2_device_register(&pci->dev, &chip->v4l2_dev);
+ if (err < 0) {
+ snd_fm801_free(chip);
+ 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");
+ snd_fm801_free(chip);
+ 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)) {
+ strlcpy(chip->tea.card, get_tea575x_gpio(chip)->name,
+ sizeof(chip->tea.card));
+ }
+#endif
+
+ *rchip = chip;
+ 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+ if ((err = snd_fm801_create(card, pci, tea575x_tuner[dev], radio_nr[dev], &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ strcpy(card->driver, "FM801");
+ strcpy(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;
+
+ if ((err = snd_fm801_pcm(chip, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_fm801_mixer(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((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)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_opl3_create(card, chip->port + FM801_OPL3_BANK0,
+ chip->port + FM801_OPL3_BANK1,
+ OPL3_HW_OPL3_FM801, 1, &opl3)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ __fm801_tuner_only:
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_card_fm801_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(snd_fm801_pm, snd_fm801_suspend, snd_fm801_resume);
+#define SND_FM801_PM_OPS &snd_fm801_pm
+#else
+#define SND_FM801_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver fm801_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_fm801_ids,
+ .probe = snd_card_fm801_probe,
+ .remove = snd_card_fm801_remove,
+ .driver = {
+ .pm = SND_FM801_PM_OPS,
+ },
+};
+
+module_pci_driver(fm801_driver);
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
new file mode 100644
index 000000000..90759391c
--- /dev/null
+++ b/sound/pci/hda/Kconfig
@@ -0,0 +1,259 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "HD-Audio"
+
+config SND_HDA
+ tristate
+ select SND_PCM
+ select SND_VMASTER
+ select SND_JACK
+ select SND_HDA_CORE
+
+config SND_HDA_GENERIC_LEDS
+ bool
+
+config SND_HDA_INTEL
+ tristate "HD Audio PCI"
+ depends on SND_PCI
+ select SND_HDA
+ select SND_INTEL_DSP_CONFIG
+ help
+ Say Y here to include support for Intel "High Definition
+ Audio" (Azalia) and its compatible devices.
+
+ This option enables the HD-audio controller. Don't forget
+ to choose the appropriate codec options below.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-hda-intel.
+
+config SND_HDA_TEGRA
+ tristate "NVIDIA Tegra HD Audio"
+ depends on ARCH_TEGRA
+ select SND_HDA
+ select SND_HDA_ALIGNED_MMIO
+ help
+ Say Y here to support the HDA controller present in NVIDIA
+ Tegra SoCs
+
+ This options enables support for the HD Audio controller
+ present in some NVIDIA Tegra SoCs, used to communicate audio
+ to the HDMI output.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-hda-tegra.
+
+if SND_HDA
+
+config SND_HDA_HWDEP
+ bool "Build hwdep interface for HD-audio driver"
+ select SND_HWDEP
+ help
+ Say Y here to build a hwdep interface for HD-audio driver.
+ This interface can be used for out-of-band communication
+ with codecs for debugging purposes.
+
+config SND_HDA_RECONFIG
+ bool "Allow dynamic codec reconfiguration"
+ help
+ Say Y here to enable the HD-audio codec re-configuration feature.
+ It allows user to clear the whole codec configuration, change the
+ codec setup, add extra verbs, and re-configure the codec dynamically.
+
+ Note that this item alone doesn't provide the sysfs interface, but
+ enables the feature just for the patch loader below.
+ If you need the traditional sysfs entries for the manual interaction,
+ turn on CONFIG_SND_HDA_HWDEP as well.
+
+config SND_HDA_INPUT_BEEP
+ bool "Support digital beep via input layer"
+ depends on INPUT=y || INPUT=SND_HDA
+ help
+ Say Y here to build a digital beep interface for HD-audio
+ driver. This interface is used to generate digital beeps.
+
+config SND_HDA_INPUT_BEEP_MODE
+ int "Digital beep registration mode (0=off, 1=on)"
+ depends on SND_HDA_INPUT_BEEP=y
+ default "1"
+ range 0 1
+ help
+ Set 0 to disable the digital beep interface for HD-audio by default.
+ Set 1 to always enable the digital beep interface for HD-audio by
+ default.
+
+config SND_HDA_PATCH_LOADER
+ bool "Support initialization patch loading for HD-audio"
+ select FW_LOADER
+ select SND_HDA_RECONFIG
+ help
+ Say Y here to allow the HD-audio driver to load a pseudo
+ firmware file ("patch") for overriding the BIOS setup at
+ start up. The "patch" file can be specified via patch module
+ option, such as patch=hda-init.
+
+config SND_HDA_CODEC_REALTEK
+ tristate "Build Realtek HD-audio codec support"
+ select SND_HDA_GENERIC
+ select SND_HDA_GENERIC_LEDS
+ help
+ Say Y or M here to include Realtek HD-audio codec support in
+ snd-hda-intel driver, such as ALC880.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_REALTEK=m
+
+config SND_HDA_CODEC_ANALOG
+ tristate "Build Analog Devices HD-audio codec support"
+ select SND_HDA_GENERIC
+ help
+ Say Y or M here to include Analog Devices HD-audio codec support in
+ snd-hda-intel driver, such as AD1986A.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_ANALOG=m
+
+config SND_HDA_CODEC_SIGMATEL
+ tristate "Build IDT/Sigmatel HD-audio codec support"
+ select SND_HDA_GENERIC
+ select SND_HDA_GENERIC_LEDS
+ help
+ Say Y or M here to include IDT (Sigmatel) HD-audio codec support in
+ snd-hda-intel driver, such as STAC9200.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_SIGMATEL=m
+
+config SND_HDA_CODEC_VIA
+ tristate "Build VIA HD-audio codec support"
+ select SND_HDA_GENERIC
+ help
+ Say Y or M here to include VIA HD-audio codec support in
+ snd-hda-intel driver, such as VT1708.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_VIA=m
+
+config SND_HDA_CODEC_HDMI
+ tristate "Build HDMI/DisplayPort HD-audio codec support"
+ select SND_DYNAMIC_MINORS
+ help
+ Say Y or M here to include HDMI and DisplayPort HD-audio codec
+ support in snd-hda-intel driver. This includes all AMD/ATI,
+ Intel and Nvidia HDMI/DisplayPort codecs.
+
+ Note that this option mandatorily enables CONFIG_SND_DYNAMIC_MINORS
+ to assure the multiple streams for DP-MST support.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_HDMI=m
+
+config SND_HDA_CODEC_CIRRUS
+ tristate "Build Cirrus Logic codec support"
+ select SND_HDA_GENERIC
+ help
+ Say Y or M here to include Cirrus Logic codec support in
+ snd-hda-intel driver, such as CS4206.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_CIRRUS=m
+
+config SND_HDA_CODEC_CONEXANT
+ tristate "Build Conexant HD-audio codec support"
+ select SND_HDA_GENERIC
+ select SND_HDA_GENERIC_LEDS
+ help
+ Say Y or M here to include Conexant HD-audio codec support in
+ snd-hda-intel driver, such as CX20549.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_CONEXANT=m
+
+config SND_HDA_CODEC_CA0110
+ tristate "Build Creative CA0110-IBG codec support"
+ select SND_HDA_GENERIC
+ help
+ Say Y or M here to include Creative CA0110-IBG codec support in
+ snd-hda-intel driver, found on some Creative X-Fi cards.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_CA0110=m
+
+config SND_HDA_CODEC_CA0132
+ tristate "Build Creative CA0132 codec support"
+ help
+ Say Y or M here to include Creative CA0132 codec support in
+ snd-hda-intel driver.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_CA0132=m
+
+config SND_HDA_CODEC_CA0132_DSP
+ bool "Support new DSP code for CA0132 codec"
+ depends on SND_HDA_CODEC_CA0132
+ default y
+ select SND_HDA_DSP_LOADER
+ select FW_LOADER
+ help
+ Say Y here to enable the DSP for Creative CA0132 for extended
+ features like equalizer or echo cancellation.
+
+ Note that this option requires the external firmware file
+ (ctefx.bin).
+
+config SND_HDA_CODEC_CMEDIA
+ tristate "Build C-Media HD-audio codec support"
+ select SND_HDA_GENERIC
+ help
+ Say Y or M here to include C-Media HD-audio codec support in
+ snd-hda-intel driver, such as CMI9880.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_CMEDIA=m
+
+config SND_HDA_CODEC_SI3054
+ tristate "Build Silicon Labs 3054 HD-modem codec support"
+ help
+ Say Y or M here to include Silicon Labs 3054 HD-modem codec
+ (and compatibles) support in snd-hda-intel driver.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_CODEC_SI3054=m
+
+config SND_HDA_GENERIC
+ tristate "Enable generic HD-audio codec parser"
+ select NEW_LEDS if SND_HDA_GENERIC_LEDS
+ select LEDS_CLASS if SND_HDA_GENERIC_LEDS
+ select LEDS_TRIGGERS if SND_HDA_GENERIC_LEDS
+ select LEDS_TRIGGER_AUDIO if SND_HDA_GENERIC_LEDS
+ help
+ Say Y or M here to enable the generic HD-audio codec parser
+ in snd-hda-intel driver.
+
+comment "Set to Y if you want auto-loading the codec driver"
+ depends on SND_HDA=y && SND_HDA_GENERIC=m
+
+config SND_HDA_POWER_SAVE_DEFAULT
+ int "Default time-out for HD-audio power-save mode"
+ depends on PM
+ default 0
+ help
+ The default time-out value in seconds for HD-audio automatic
+ power-save mode. 0 means to disable the power-save mode.
+
+config SND_HDA_INTEL_HDMI_SILENT_STREAM
+ bool "Enable Silent Stream always for HDMI"
+ depends on SND_HDA_INTEL
+ help
+ Intel hardware has a feature called 'silent stream', that
+ keeps external HDMI receiver's analog circuitry powered on
+ avoiding 2-3 sec silence during playback start. This mechanism
+ relies on setting channel_id as 0xf, sending info packet and
+ preventing codec D3 entry (increasing platform static power
+ consumption when HDMI receiver is plugged-in). 2-3 sec silence
+ at the playback start is expected whenever there is format change.
+ (default is 2 channel format).
+ Say Y to enable Silent Stream feature.
+
+endif
+
+endmenu
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
new file mode 100644
index 000000000..b57432f00
--- /dev/null
+++ b/sound/pci/hda/Makefile
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-hda-intel-objs := hda_intel.o
+snd-hda-tegra-objs := hda_tegra.o
+
+snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
+snd-hda-codec-y += hda_controller.o
+snd-hda-codec-$(CONFIG_SND_PROC_FS) += hda_proc.o
+
+snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o
+snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o
+
+# for trace-points
+CFLAGS_hda_controller.o := -I$(src)
+CFLAGS_hda_intel.o := -I$(src)
+
+snd-hda-codec-generic-objs := hda_generic.o
+snd-hda-codec-realtek-objs := patch_realtek.o
+snd-hda-codec-cmedia-objs := patch_cmedia.o
+snd-hda-codec-analog-objs := patch_analog.o
+snd-hda-codec-idt-objs := patch_sigmatel.o
+snd-hda-codec-si3054-objs := patch_si3054.o
+snd-hda-codec-cirrus-objs := patch_cirrus.o
+snd-hda-codec-ca0110-objs := patch_ca0110.o
+snd-hda-codec-ca0132-objs := patch_ca0132.o
+snd-hda-codec-conexant-objs := patch_conexant.o
+snd-hda-codec-via-objs := patch_via.o
+snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o
+
+# common driver
+obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
+
+# codec drivers
+obj-$(CONFIG_SND_HDA_GENERIC) += snd-hda-codec-generic.o
+obj-$(CONFIG_SND_HDA_CODEC_REALTEK) += snd-hda-codec-realtek.o
+obj-$(CONFIG_SND_HDA_CODEC_CMEDIA) += snd-hda-codec-cmedia.o
+obj-$(CONFIG_SND_HDA_CODEC_ANALOG) += snd-hda-codec-analog.o
+obj-$(CONFIG_SND_HDA_CODEC_SIGMATEL) += snd-hda-codec-idt.o
+obj-$(CONFIG_SND_HDA_CODEC_SI3054) += snd-hda-codec-si3054.o
+obj-$(CONFIG_SND_HDA_CODEC_CIRRUS) += snd-hda-codec-cirrus.o
+obj-$(CONFIG_SND_HDA_CODEC_CA0110) += snd-hda-codec-ca0110.o
+obj-$(CONFIG_SND_HDA_CODEC_CA0132) += snd-hda-codec-ca0132.o
+obj-$(CONFIG_SND_HDA_CODEC_CONEXANT) += snd-hda-codec-conexant.o
+obj-$(CONFIG_SND_HDA_CODEC_VIA) += snd-hda-codec-via.o
+obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o
+
+# this must be the last entry after codec drivers;
+# otherwise the codec patches won't be hooked before the PCI probe
+# when built in kernel
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
+obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o
diff --git a/sound/pci/hda/ca0132_regs.h b/sound/pci/hda/ca0132_regs.h
new file mode 100644
index 000000000..0ead571fb
--- /dev/null
+++ b/sound/pci/hda/ca0132_regs.h
@@ -0,0 +1,396 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * HD audio interface patch for Creative CA0132 chip.
+ * CA0132 registers defines.
+ *
+ * Copyright (c) 2011, Creative Technology Ltd.
+ */
+
+#ifndef __CA0132_REGS_H
+#define __CA0132_REGS_H
+
+#define DSP_CHIP_OFFSET 0x100000
+#define DSP_DBGCNTL_MODULE_OFFSET 0xE30
+#define DSP_DBGCNTL_INST_OFFSET \
+ (DSP_CHIP_OFFSET + DSP_DBGCNTL_MODULE_OFFSET)
+
+#define DSP_DBGCNTL_EXEC_LOBIT 0x0
+#define DSP_DBGCNTL_EXEC_HIBIT 0x3
+#define DSP_DBGCNTL_EXEC_MASK 0xF
+
+#define DSP_DBGCNTL_SS_LOBIT 0x4
+#define DSP_DBGCNTL_SS_HIBIT 0x7
+#define DSP_DBGCNTL_SS_MASK 0xF0
+
+#define DSP_DBGCNTL_STATE_LOBIT 0xA
+#define DSP_DBGCNTL_STATE_HIBIT 0xD
+#define DSP_DBGCNTL_STATE_MASK 0x3C00
+
+#define XRAM_CHIP_OFFSET 0x0
+#define XRAM_XRAM_CHANNEL_COUNT 0xE000
+#define XRAM_XRAM_MODULE_OFFSET 0x0
+#define XRAM_XRAM_CHAN_INCR 4
+#define XRAM_XRAM_INST_OFFSET(_chan) \
+ (XRAM_CHIP_OFFSET + XRAM_XRAM_MODULE_OFFSET + \
+ (_chan * XRAM_XRAM_CHAN_INCR))
+
+#define YRAM_CHIP_OFFSET 0x40000
+#define YRAM_YRAM_CHANNEL_COUNT 0x8000
+#define YRAM_YRAM_MODULE_OFFSET 0x0
+#define YRAM_YRAM_CHAN_INCR 4
+#define YRAM_YRAM_INST_OFFSET(_chan) \
+ (YRAM_CHIP_OFFSET + YRAM_YRAM_MODULE_OFFSET + \
+ (_chan * YRAM_YRAM_CHAN_INCR))
+
+#define UC_CHIP_OFFSET 0x80000
+#define UC_UC_CHANNEL_COUNT 0x10000
+#define UC_UC_MODULE_OFFSET 0x0
+#define UC_UC_CHAN_INCR 4
+#define UC_UC_INST_OFFSET(_chan) \
+ (UC_CHIP_OFFSET + UC_UC_MODULE_OFFSET + \
+ (_chan * UC_UC_CHAN_INCR))
+
+#define AXRAM_CHIP_OFFSET 0x3C000
+#define AXRAM_AXRAM_CHANNEL_COUNT 0x1000
+#define AXRAM_AXRAM_MODULE_OFFSET 0x0
+#define AXRAM_AXRAM_CHAN_INCR 4
+#define AXRAM_AXRAM_INST_OFFSET(_chan) \
+ (AXRAM_CHIP_OFFSET + AXRAM_AXRAM_MODULE_OFFSET + \
+ (_chan * AXRAM_AXRAM_CHAN_INCR))
+
+#define AYRAM_CHIP_OFFSET 0x78000
+#define AYRAM_AYRAM_CHANNEL_COUNT 0x1000
+#define AYRAM_AYRAM_MODULE_OFFSET 0x0
+#define AYRAM_AYRAM_CHAN_INCR 4
+#define AYRAM_AYRAM_INST_OFFSET(_chan) \
+ (AYRAM_CHIP_OFFSET + AYRAM_AYRAM_MODULE_OFFSET + \
+ (_chan * AYRAM_AYRAM_CHAN_INCR))
+
+#define DSPDMAC_CHIP_OFFSET 0x110000
+#define DSPDMAC_DMA_CFG_CHANNEL_COUNT 12
+#define DSPDMAC_DMACFG_MODULE_OFFSET 0xF00
+#define DSPDMAC_DMACFG_CHAN_INCR 0x10
+#define DSPDMAC_DMACFG_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_DMACFG_MODULE_OFFSET + \
+ (_chan * DSPDMAC_DMACFG_CHAN_INCR))
+
+#define DSPDMAC_DMACFG_DBADR_LOBIT 0x0
+#define DSPDMAC_DMACFG_DBADR_HIBIT 0x10
+#define DSPDMAC_DMACFG_DBADR_MASK 0x1FFFF
+#define DSPDMAC_DMACFG_LP_LOBIT 0x11
+#define DSPDMAC_DMACFG_LP_HIBIT 0x11
+#define DSPDMAC_DMACFG_LP_MASK 0x20000
+
+#define DSPDMAC_DMACFG_AINCR_LOBIT 0x12
+#define DSPDMAC_DMACFG_AINCR_HIBIT 0x12
+#define DSPDMAC_DMACFG_AINCR_MASK 0x40000
+
+#define DSPDMAC_DMACFG_DWR_LOBIT 0x13
+#define DSPDMAC_DMACFG_DWR_HIBIT 0x13
+#define DSPDMAC_DMACFG_DWR_MASK 0x80000
+
+#define DSPDMAC_DMACFG_AJUMP_LOBIT 0x14
+#define DSPDMAC_DMACFG_AJUMP_HIBIT 0x17
+#define DSPDMAC_DMACFG_AJUMP_MASK 0xF00000
+
+#define DSPDMAC_DMACFG_AMODE_LOBIT 0x18
+#define DSPDMAC_DMACFG_AMODE_HIBIT 0x19
+#define DSPDMAC_DMACFG_AMODE_MASK 0x3000000
+
+#define DSPDMAC_DMACFG_LK_LOBIT 0x1A
+#define DSPDMAC_DMACFG_LK_HIBIT 0x1A
+#define DSPDMAC_DMACFG_LK_MASK 0x4000000
+
+#define DSPDMAC_DMACFG_AICS_LOBIT 0x1B
+#define DSPDMAC_DMACFG_AICS_HIBIT 0x1F
+#define DSPDMAC_DMACFG_AICS_MASK 0xF8000000
+
+#define DSPDMAC_DMACFG_LP_SINGLE 0
+#define DSPDMAC_DMACFG_LP_LOOPING 1
+
+#define DSPDMAC_DMACFG_AINCR_XANDY 0
+#define DSPDMAC_DMACFG_AINCR_XORY 1
+
+#define DSPDMAC_DMACFG_DWR_DMA_RD 0
+#define DSPDMAC_DMACFG_DWR_DMA_WR 1
+
+#define DSPDMAC_DMACFG_AMODE_LINEAR 0
+#define DSPDMAC_DMACFG_AMODE_RSV1 1
+#define DSPDMAC_DMACFG_AMODE_WINTLV 2
+#define DSPDMAC_DMACFG_AMODE_GINTLV 3
+
+#define DSPDMAC_DSP_ADR_OFS_CHANNEL_COUNT 12
+#define DSPDMAC_DSPADROFS_MODULE_OFFSET 0xF04
+#define DSPDMAC_DSPADROFS_CHAN_INCR 0x10
+#define DSPDMAC_DSPADROFS_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADROFS_MODULE_OFFSET + \
+ (_chan * DSPDMAC_DSPADROFS_CHAN_INCR))
+
+#define DSPDMAC_DSPADROFS_COFS_LOBIT 0x0
+#define DSPDMAC_DSPADROFS_COFS_HIBIT 0xF
+#define DSPDMAC_DSPADROFS_COFS_MASK 0xFFFF
+
+#define DSPDMAC_DSPADROFS_BOFS_LOBIT 0x10
+#define DSPDMAC_DSPADROFS_BOFS_HIBIT 0x1F
+#define DSPDMAC_DSPADROFS_BOFS_MASK 0xFFFF0000
+
+#define DSPDMAC_DSP_ADR_WOFS_CHANNEL_COUNT 12
+#define DSPDMAC_DSPADRWOFS_MODULE_OFFSET 0xF04
+#define DSPDMAC_DSPADRWOFS_CHAN_INCR 0x10
+
+#define DSPDMAC_DSPADRWOFS_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADRWOFS_MODULE_OFFSET + \
+ (_chan * DSPDMAC_DSPADRWOFS_CHAN_INCR))
+
+#define DSPDMAC_DSPADRWOFS_WCOFS_LOBIT 0x0
+#define DSPDMAC_DSPADRWOFS_WCOFS_HIBIT 0xA
+#define DSPDMAC_DSPADRWOFS_WCOFS_MASK 0x7FF
+
+#define DSPDMAC_DSPADRWOFS_WCBFR_LOBIT 0xB
+#define DSPDMAC_DSPADRWOFS_WCBFR_HIBIT 0xF
+#define DSPDMAC_DSPADRWOFS_WCBFR_MASK 0xF800
+
+#define DSPDMAC_DSPADRWOFS_WBOFS_LOBIT 0x10
+#define DSPDMAC_DSPADRWOFS_WBOFS_HIBIT 0x1A
+#define DSPDMAC_DSPADRWOFS_WBOFS_MASK 0x7FF0000
+
+#define DSPDMAC_DSPADRWOFS_WBBFR_LOBIT 0x1B
+#define DSPDMAC_DSPADRWOFS_WBBFR_HIBIT 0x1F
+#define DSPDMAC_DSPADRWOFS_WBBFR_MASK 0xF8000000
+
+#define DSPDMAC_DSP_ADR_GOFS_CHANNEL_COUNT 12
+#define DSPDMAC_DSPADRGOFS_MODULE_OFFSET 0xF04
+#define DSPDMAC_DSPADRGOFS_CHAN_INCR 0x10
+#define DSPDMAC_DSPADRGOFS_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADRGOFS_MODULE_OFFSET + \
+ (_chan * DSPDMAC_DSPADRGOFS_CHAN_INCR))
+
+#define DSPDMAC_DSPADRGOFS_GCOFS_LOBIT 0x0
+#define DSPDMAC_DSPADRGOFS_GCOFS_HIBIT 0x9
+#define DSPDMAC_DSPADRGOFS_GCOFS_MASK 0x3FF
+
+#define DSPDMAC_DSPADRGOFS_GCS_LOBIT 0xA
+#define DSPDMAC_DSPADRGOFS_GCS_HIBIT 0xC
+#define DSPDMAC_DSPADRGOFS_GCS_MASK 0x1C00
+
+#define DSPDMAC_DSPADRGOFS_GCBFR_LOBIT 0xD
+#define DSPDMAC_DSPADRGOFS_GCBFR_HIBIT 0xF
+#define DSPDMAC_DSPADRGOFS_GCBFR_MASK 0xE000
+
+#define DSPDMAC_DSPADRGOFS_GBOFS_LOBIT 0x10
+#define DSPDMAC_DSPADRGOFS_GBOFS_HIBIT 0x19
+#define DSPDMAC_DSPADRGOFS_GBOFS_MASK 0x3FF0000
+
+#define DSPDMAC_DSPADRGOFS_GBS_LOBIT 0x1A
+#define DSPDMAC_DSPADRGOFS_GBS_HIBIT 0x1C
+#define DSPDMAC_DSPADRGOFS_GBS_MASK 0x1C000000
+
+#define DSPDMAC_DSPADRGOFS_GBBFR_LOBIT 0x1D
+#define DSPDMAC_DSPADRGOFS_GBBFR_HIBIT 0x1F
+#define DSPDMAC_DSPADRGOFS_GBBFR_MASK 0xE0000000
+
+#define DSPDMAC_XFR_CNT_CHANNEL_COUNT 12
+#define DSPDMAC_XFRCNT_MODULE_OFFSET 0xF08
+#define DSPDMAC_XFRCNT_CHAN_INCR 0x10
+
+#define DSPDMAC_XFRCNT_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_XFRCNT_MODULE_OFFSET + \
+ (_chan * DSPDMAC_XFRCNT_CHAN_INCR))
+
+#define DSPDMAC_XFRCNT_CCNT_LOBIT 0x0
+#define DSPDMAC_XFRCNT_CCNT_HIBIT 0xF
+#define DSPDMAC_XFRCNT_CCNT_MASK 0xFFFF
+
+#define DSPDMAC_XFRCNT_BCNT_LOBIT 0x10
+#define DSPDMAC_XFRCNT_BCNT_HIBIT 0x1F
+#define DSPDMAC_XFRCNT_BCNT_MASK 0xFFFF0000
+
+#define DSPDMAC_IRQ_CNT_CHANNEL_COUNT 12
+#define DSPDMAC_IRQCNT_MODULE_OFFSET 0xF0C
+#define DSPDMAC_IRQCNT_CHAN_INCR 0x10
+#define DSPDMAC_IRQCNT_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_IRQCNT_MODULE_OFFSET + \
+ (_chan * DSPDMAC_IRQCNT_CHAN_INCR))
+
+#define DSPDMAC_IRQCNT_CICNT_LOBIT 0x0
+#define DSPDMAC_IRQCNT_CICNT_HIBIT 0xF
+#define DSPDMAC_IRQCNT_CICNT_MASK 0xFFFF
+
+#define DSPDMAC_IRQCNT_BICNT_LOBIT 0x10
+#define DSPDMAC_IRQCNT_BICNT_HIBIT 0x1F
+#define DSPDMAC_IRQCNT_BICNT_MASK 0xFFFF0000
+
+#define DSPDMAC_AUD_CHSEL_CHANNEL_COUNT 12
+#define DSPDMAC_AUDCHSEL_MODULE_OFFSET 0xFC0
+#define DSPDMAC_AUDCHSEL_CHAN_INCR 0x4
+#define DSPDMAC_AUDCHSEL_INST_OFFSET(_chan) \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_AUDCHSEL_MODULE_OFFSET + \
+ (_chan * DSPDMAC_AUDCHSEL_CHAN_INCR))
+
+#define DSPDMAC_AUDCHSEL_ACS_LOBIT 0x0
+#define DSPDMAC_AUDCHSEL_ACS_HIBIT 0x1F
+#define DSPDMAC_AUDCHSEL_ACS_MASK 0xFFFFFFFF
+
+#define DSPDMAC_CHNLSTART_MODULE_OFFSET 0xFF0
+#define DSPDMAC_CHNLSTART_INST_OFFSET \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLSTART_MODULE_OFFSET)
+
+#define DSPDMAC_CHNLSTART_EN_LOBIT 0x0
+#define DSPDMAC_CHNLSTART_EN_HIBIT 0xB
+#define DSPDMAC_CHNLSTART_EN_MASK 0xFFF
+
+#define DSPDMAC_CHNLSTART_VAI1_LOBIT 0xC
+#define DSPDMAC_CHNLSTART_VAI1_HIBIT 0xF
+#define DSPDMAC_CHNLSTART_VAI1_MASK 0xF000
+
+#define DSPDMAC_CHNLSTART_DIS_LOBIT 0x10
+#define DSPDMAC_CHNLSTART_DIS_HIBIT 0x1B
+#define DSPDMAC_CHNLSTART_DIS_MASK 0xFFF0000
+
+#define DSPDMAC_CHNLSTART_VAI2_LOBIT 0x1C
+#define DSPDMAC_CHNLSTART_VAI2_HIBIT 0x1F
+#define DSPDMAC_CHNLSTART_VAI2_MASK 0xF0000000
+
+#define DSPDMAC_CHNLSTATUS_MODULE_OFFSET 0xFF4
+#define DSPDMAC_CHNLSTATUS_INST_OFFSET \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLSTATUS_MODULE_OFFSET)
+
+#define DSPDMAC_CHNLSTATUS_ISC_LOBIT 0x0
+#define DSPDMAC_CHNLSTATUS_ISC_HIBIT 0xB
+#define DSPDMAC_CHNLSTATUS_ISC_MASK 0xFFF
+
+#define DSPDMAC_CHNLSTATUS_AOO_LOBIT 0xC
+#define DSPDMAC_CHNLSTATUS_AOO_HIBIT 0xC
+#define DSPDMAC_CHNLSTATUS_AOO_MASK 0x1000
+
+#define DSPDMAC_CHNLSTATUS_AOU_LOBIT 0xD
+#define DSPDMAC_CHNLSTATUS_AOU_HIBIT 0xD
+#define DSPDMAC_CHNLSTATUS_AOU_MASK 0x2000
+
+#define DSPDMAC_CHNLSTATUS_AIO_LOBIT 0xE
+#define DSPDMAC_CHNLSTATUS_AIO_HIBIT 0xE
+#define DSPDMAC_CHNLSTATUS_AIO_MASK 0x4000
+
+#define DSPDMAC_CHNLSTATUS_AIU_LOBIT 0xF
+#define DSPDMAC_CHNLSTATUS_AIU_HIBIT 0xF
+#define DSPDMAC_CHNLSTATUS_AIU_MASK 0x8000
+
+#define DSPDMAC_CHNLSTATUS_IEN_LOBIT 0x10
+#define DSPDMAC_CHNLSTATUS_IEN_HIBIT 0x1B
+#define DSPDMAC_CHNLSTATUS_IEN_MASK 0xFFF0000
+
+#define DSPDMAC_CHNLSTATUS_VAI0_LOBIT 0x1C
+#define DSPDMAC_CHNLSTATUS_VAI0_HIBIT 0x1F
+#define DSPDMAC_CHNLSTATUS_VAI0_MASK 0xF0000000
+
+#define DSPDMAC_CHNLPROP_MODULE_OFFSET 0xFF8
+#define DSPDMAC_CHNLPROP_INST_OFFSET \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLPROP_MODULE_OFFSET)
+
+#define DSPDMAC_CHNLPROP_DCON_LOBIT 0x0
+#define DSPDMAC_CHNLPROP_DCON_HIBIT 0xB
+#define DSPDMAC_CHNLPROP_DCON_MASK 0xFFF
+
+#define DSPDMAC_CHNLPROP_FFS_LOBIT 0xC
+#define DSPDMAC_CHNLPROP_FFS_HIBIT 0xC
+#define DSPDMAC_CHNLPROP_FFS_MASK 0x1000
+
+#define DSPDMAC_CHNLPROP_NAJ_LOBIT 0xD
+#define DSPDMAC_CHNLPROP_NAJ_HIBIT 0xD
+#define DSPDMAC_CHNLPROP_NAJ_MASK 0x2000
+
+#define DSPDMAC_CHNLPROP_ENH_LOBIT 0xE
+#define DSPDMAC_CHNLPROP_ENH_HIBIT 0xE
+#define DSPDMAC_CHNLPROP_ENH_MASK 0x4000
+
+#define DSPDMAC_CHNLPROP_MSPCE_LOBIT 0x10
+#define DSPDMAC_CHNLPROP_MSPCE_HIBIT 0x1B
+#define DSPDMAC_CHNLPROP_MSPCE_MASK 0xFFF0000
+
+#define DSPDMAC_CHNLPROP_AC_LOBIT 0x1C
+#define DSPDMAC_CHNLPROP_AC_HIBIT 0x1F
+#define DSPDMAC_CHNLPROP_AC_MASK 0xF0000000
+
+#define DSPDMAC_ACTIVE_MODULE_OFFSET 0xFFC
+#define DSPDMAC_ACTIVE_INST_OFFSET \
+ (DSPDMAC_CHIP_OFFSET + DSPDMAC_ACTIVE_MODULE_OFFSET)
+
+#define DSPDMAC_ACTIVE_AAR_LOBIT 0x0
+#define DSPDMAC_ACTIVE_AAR_HIBIT 0xB
+#define DSPDMAC_ACTIVE_AAR_MASK 0xFFF
+
+#define DSPDMAC_ACTIVE_WFR_LOBIT 0xC
+#define DSPDMAC_ACTIVE_WFR_HIBIT 0x17
+#define DSPDMAC_ACTIVE_WFR_MASK 0xFFF000
+
+#define DSP_AUX_MEM_BASE 0xE000
+#define INVALID_CHIP_ADDRESS (~0U)
+
+#define X_SIZE (XRAM_XRAM_CHANNEL_COUNT * XRAM_XRAM_CHAN_INCR)
+#define Y_SIZE (YRAM_YRAM_CHANNEL_COUNT * YRAM_YRAM_CHAN_INCR)
+#define AX_SIZE (AXRAM_AXRAM_CHANNEL_COUNT * AXRAM_AXRAM_CHAN_INCR)
+#define AY_SIZE (AYRAM_AYRAM_CHANNEL_COUNT * AYRAM_AYRAM_CHAN_INCR)
+#define UC_SIZE (UC_UC_CHANNEL_COUNT * UC_UC_CHAN_INCR)
+
+#define XEXT_SIZE (X_SIZE + AX_SIZE)
+#define YEXT_SIZE (Y_SIZE + AY_SIZE)
+
+#define U64K 0x10000UL
+
+#define X_END (XRAM_CHIP_OFFSET + X_SIZE)
+#define X_EXT (XRAM_CHIP_OFFSET + XEXT_SIZE)
+#define AX_END (XRAM_CHIP_OFFSET + U64K*4)
+
+#define Y_END (YRAM_CHIP_OFFSET + Y_SIZE)
+#define Y_EXT (YRAM_CHIP_OFFSET + YEXT_SIZE)
+#define AY_END (YRAM_CHIP_OFFSET + U64K*4)
+
+#define UC_END (UC_CHIP_OFFSET + UC_SIZE)
+
+#define X_RANGE_MAIN(a, s) \
+ (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < X_END))
+#define X_RANGE_AUX(a, s) \
+ (((a) >= X_END) && ((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < AX_END))
+#define X_RANGE_EXT(a, s) \
+ (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < X_EXT))
+#define X_RANGE_ALL(a, s) \
+ (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < AX_END))
+
+#define Y_RANGE_MAIN(a, s) \
+ (((a) >= YRAM_CHIP_OFFSET) && \
+ ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < Y_END))
+#define Y_RANGE_AUX(a, s) \
+ (((a) >= Y_END) && \
+ ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < AY_END))
+#define Y_RANGE_EXT(a, s) \
+ (((a) >= YRAM_CHIP_OFFSET) && \
+ ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < Y_EXT))
+#define Y_RANGE_ALL(a, s) \
+ (((a) >= YRAM_CHIP_OFFSET) && \
+ ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < AY_END))
+
+#define UC_RANGE(a, s) \
+ (((a) >= UC_CHIP_OFFSET) && \
+ ((a)+((s)-1)*UC_UC_CHAN_INCR < UC_END))
+
+#define X_OFF(a) \
+ (((a) - XRAM_CHIP_OFFSET) / XRAM_XRAM_CHAN_INCR)
+#define AX_OFF(a) \
+ (((a) % (AXRAM_AXRAM_CHANNEL_COUNT * \
+ AXRAM_AXRAM_CHAN_INCR)) / AXRAM_AXRAM_CHAN_INCR)
+
+#define Y_OFF(a) \
+ (((a) - YRAM_CHIP_OFFSET) / YRAM_YRAM_CHAN_INCR)
+#define AY_OFF(a) \
+ (((a) % (AYRAM_AYRAM_CHANNEL_COUNT * \
+ AYRAM_AYRAM_CHAN_INCR)) / AYRAM_AYRAM_CHAN_INCR)
+
+#define UC_OFF(a) (((a) - UC_CHIP_OFFSET) / UC_UC_CHAN_INCR)
+
+#define X_EXT_MAIN_SIZE(a) (XRAM_XRAM_CHANNEL_COUNT - X_OFF(a))
+#define X_EXT_AUX_SIZE(a, s) ((s) - X_EXT_MAIN_SIZE(a))
+
+#define Y_EXT_MAIN_SIZE(a) (YRAM_YRAM_CHANNEL_COUNT - Y_OFF(a))
+#define Y_EXT_AUX_SIZE(a, s) ((s) - Y_EXT_MAIN_SIZE(a))
+
+#endif
diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c
new file mode 100644
index 000000000..ec821a263
--- /dev/null
+++ b/sound/pci/hda/hda_auto_parser.c
@@ -0,0 +1,1047 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BIOS auto-parser helper functions for HD-audio
+ *
+ * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/sort.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+
+/*
+ * Helper for automatic pin configuration
+ */
+
+static int is_in_nid_list(hda_nid_t nid, const hda_nid_t *list)
+{
+ for (; *list; list++)
+ if (*list == nid)
+ return 1;
+ return 0;
+}
+
+/* a pair of input pin and its sequence */
+struct auto_out_pin {
+ hda_nid_t pin;
+ short seq;
+};
+
+static int compare_seq(const void *ap, const void *bp)
+{
+ const struct auto_out_pin *a = ap;
+ const struct auto_out_pin *b = bp;
+ return (int)(a->seq - b->seq);
+}
+
+/*
+ * Sort an associated group of pins according to their sequence numbers.
+ * then store it to a pin array.
+ */
+static void sort_pins_by_sequence(hda_nid_t *pins, struct auto_out_pin *list,
+ int num_pins)
+{
+ int i;
+ sort(list, num_pins, sizeof(list[0]), compare_seq, NULL);
+ for (i = 0; i < num_pins; i++)
+ pins[i] = list[i].pin;
+}
+
+
+/* add the found input-pin to the cfg->inputs[] table */
+static void add_auto_cfg_input_pin(struct hda_codec *codec, struct auto_pin_cfg *cfg,
+ hda_nid_t nid, int type)
+{
+ if (cfg->num_inputs < AUTO_CFG_MAX_INS) {
+ cfg->inputs[cfg->num_inputs].pin = nid;
+ cfg->inputs[cfg->num_inputs].type = type;
+ cfg->inputs[cfg->num_inputs].has_boost_on_pin =
+ nid_has_volume(codec, nid, HDA_INPUT);
+ cfg->num_inputs++;
+ }
+}
+
+static int compare_input_type(const void *ap, const void *bp)
+{
+ const struct auto_pin_cfg_item *a = ap;
+ const struct auto_pin_cfg_item *b = bp;
+ if (a->type != b->type)
+ return (int)(a->type - b->type);
+
+ /* If has both hs_mic and hp_mic, pick the hs_mic ahead of hp_mic. */
+ if (a->is_headset_mic && b->is_headphone_mic)
+ return -1; /* don't swap */
+ else if (a->is_headphone_mic && b->is_headset_mic)
+ return 1; /* swap */
+
+ /* In case one has boost and the other one has not,
+ pick the one with boost first. */
+ return (int)(b->has_boost_on_pin - a->has_boost_on_pin);
+}
+
+/* Reorder the surround channels
+ * ALSA sequence is front/surr/clfe/side
+ * HDA sequence is:
+ * 4-ch: front/surr => OK as it is
+ * 6-ch: front/clfe/surr
+ * 8-ch: front/clfe/rear/side|fc
+ */
+static void reorder_outputs(unsigned int nums, hda_nid_t *pins)
+{
+ hda_nid_t nid;
+
+ switch (nums) {
+ case 3:
+ case 4:
+ nid = pins[1];
+ pins[1] = pins[2];
+ pins[2] = nid;
+ break;
+ }
+}
+
+/* check whether the given pin has a proper pin I/O capability bit */
+static bool check_pincap_validity(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int dev)
+{
+ unsigned int pincap = snd_hda_query_pin_caps(codec, pin);
+
+ /* some old hardware don't return the proper pincaps */
+ if (!pincap)
+ return true;
+
+ switch (dev) {
+ case AC_JACK_LINE_OUT:
+ case AC_JACK_SPEAKER:
+ case AC_JACK_HP_OUT:
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ return !!(pincap & AC_PINCAP_OUT);
+ default:
+ return !!(pincap & AC_PINCAP_IN);
+ }
+}
+
+static bool can_be_headset_mic(struct hda_codec *codec,
+ struct auto_pin_cfg_item *item,
+ int seq_number)
+{
+ int attr;
+ unsigned int def_conf;
+ if (item->type != AUTO_PIN_MIC)
+ return false;
+
+ if (item->is_headset_mic || item->is_headphone_mic)
+ return false; /* Already assigned */
+
+ def_conf = snd_hda_codec_get_pincfg(codec, item->pin);
+ attr = snd_hda_get_input_pin_attr(def_conf);
+ if (attr <= INPUT_PIN_ATTR_DOCK)
+ return false;
+
+ if (seq_number >= 0) {
+ int seq = get_defcfg_sequence(def_conf);
+ if (seq != seq_number)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Parse all pin widgets and store the useful pin nids to cfg
+ *
+ * The number of line-outs or any primary output is stored in line_outs,
+ * and the corresponding output pins are assigned to line_out_pins[],
+ * in the order of front, rear, CLFE, side, ...
+ *
+ * If more extra outputs (speaker and headphone) are found, the pins are
+ * assisnged to hp_pins[] and speaker_pins[], respectively. If no line-out jack
+ * is detected, one of speaker of HP pins is assigned as the primary
+ * output, i.e. to line_out_pins[0]. So, line_outs is always positive
+ * if any analog output exists.
+ *
+ * The analog input pins are assigned to inputs array.
+ * The digital input/output pins are assigned to dig_in_pin and dig_out_pin,
+ * respectively.
+ */
+int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg,
+ const hda_nid_t *ignore_nids,
+ unsigned int cond_flags)
+{
+ hda_nid_t nid;
+ short seq, assoc_line_out;
+ struct auto_out_pin line_out[ARRAY_SIZE(cfg->line_out_pins)];
+ struct auto_out_pin speaker_out[ARRAY_SIZE(cfg->speaker_pins)];
+ struct auto_out_pin hp_out[ARRAY_SIZE(cfg->hp_pins)];
+ int i;
+
+ if (!snd_hda_get_int_hint(codec, "parser_flags", &i))
+ cond_flags = i;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ memset(line_out, 0, sizeof(line_out));
+ memset(speaker_out, 0, sizeof(speaker_out));
+ memset(hp_out, 0, sizeof(hp_out));
+ assoc_line_out = 0;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wid_caps = get_wcaps(codec, nid);
+ unsigned int wid_type = get_wcaps_type(wid_caps);
+ unsigned int def_conf;
+ short assoc, loc, conn, dev;
+
+ /* read all default configuration for pin complex */
+ if (wid_type != AC_WID_PIN)
+ continue;
+ /* ignore the given nids (e.g. pc-beep returns error) */
+ if (ignore_nids && is_in_nid_list(nid, ignore_nids))
+ continue;
+
+ def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ conn = get_defcfg_connect(def_conf);
+ if (conn == AC_JACK_PORT_NONE)
+ continue;
+ loc = get_defcfg_location(def_conf);
+ dev = get_defcfg_device(def_conf);
+
+ /* workaround for buggy BIOS setups */
+ if (dev == AC_JACK_LINE_OUT) {
+ if (conn == AC_JACK_PORT_FIXED ||
+ conn == AC_JACK_PORT_BOTH)
+ dev = AC_JACK_SPEAKER;
+ }
+
+ if (!check_pincap_validity(codec, nid, dev))
+ continue;
+
+ switch (dev) {
+ case AC_JACK_LINE_OUT:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+
+ if (!(wid_caps & AC_WCAP_STEREO))
+ if (!cfg->mono_out_pin)
+ cfg->mono_out_pin = nid;
+ if (!assoc)
+ continue;
+ if (!assoc_line_out)
+ assoc_line_out = assoc;
+ else if (assoc_line_out != assoc) {
+ codec_info(codec,
+ "ignore pin 0x%x with mismatching assoc# 0x%x vs 0x%x\n",
+ nid, assoc, assoc_line_out);
+ continue;
+ }
+ if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ line_out[cfg->line_outs].pin = nid;
+ line_out[cfg->line_outs].seq = seq;
+ cfg->line_outs++;
+ break;
+ case AC_JACK_SPEAKER:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+ if (cfg->speaker_outs >= ARRAY_SIZE(cfg->speaker_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ speaker_out[cfg->speaker_outs].pin = nid;
+ speaker_out[cfg->speaker_outs].seq = (assoc << 4) | seq;
+ cfg->speaker_outs++;
+ break;
+ case AC_JACK_HP_OUT:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+ if (cfg->hp_outs >= ARRAY_SIZE(cfg->hp_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ hp_out[cfg->hp_outs].pin = nid;
+ hp_out[cfg->hp_outs].seq = (assoc << 4) | seq;
+ cfg->hp_outs++;
+ break;
+ case AC_JACK_MIC_IN:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_MIC);
+ break;
+ case AC_JACK_LINE_IN:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_LINE_IN);
+ break;
+ case AC_JACK_CD:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_CD);
+ break;
+ case AC_JACK_AUX:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_AUX);
+ break;
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ if (cfg->dig_outs >= ARRAY_SIZE(cfg->dig_out_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ cfg->dig_out_pins[cfg->dig_outs] = nid;
+ cfg->dig_out_type[cfg->dig_outs] =
+ (loc == AC_JACK_LOC_HDMI) ?
+ HDA_PCM_TYPE_HDMI : HDA_PCM_TYPE_SPDIF;
+ cfg->dig_outs++;
+ break;
+ case AC_JACK_SPDIF_IN:
+ case AC_JACK_DIG_OTHER_IN:
+ cfg->dig_in_pin = nid;
+ if (loc == AC_JACK_LOC_HDMI)
+ cfg->dig_in_type = HDA_PCM_TYPE_HDMI;
+ else
+ cfg->dig_in_type = HDA_PCM_TYPE_SPDIF;
+ break;
+ }
+ }
+
+ /* Find a pin that could be a headset or headphone mic */
+ if (cond_flags & HDA_PINCFG_HEADSET_MIC || cond_flags & HDA_PINCFG_HEADPHONE_MIC) {
+ bool hsmic = !!(cond_flags & HDA_PINCFG_HEADSET_MIC);
+ bool hpmic = !!(cond_flags & HDA_PINCFG_HEADPHONE_MIC);
+ for (i = 0; (hsmic || hpmic) && (i < cfg->num_inputs); i++)
+ if (hsmic && can_be_headset_mic(codec, &cfg->inputs[i], 0xc)) {
+ cfg->inputs[i].is_headset_mic = 1;
+ hsmic = false;
+ } else if (hpmic && can_be_headset_mic(codec, &cfg->inputs[i], 0xd)) {
+ cfg->inputs[i].is_headphone_mic = 1;
+ hpmic = false;
+ }
+
+ /* If we didn't find our sequence number mark, fall back to any sequence number */
+ for (i = 0; (hsmic || hpmic) && (i < cfg->num_inputs); i++) {
+ if (!can_be_headset_mic(codec, &cfg->inputs[i], -1))
+ continue;
+ if (hsmic) {
+ cfg->inputs[i].is_headset_mic = 1;
+ hsmic = false;
+ } else if (hpmic) {
+ cfg->inputs[i].is_headphone_mic = 1;
+ hpmic = false;
+ }
+ }
+
+ if (hsmic)
+ codec_dbg(codec, "Told to look for a headset mic, but didn't find any.\n");
+ if (hpmic)
+ codec_dbg(codec, "Told to look for a headphone mic, but didn't find any.\n");
+ }
+
+ /* FIX-UP:
+ * If no line-out is defined but multiple HPs are found,
+ * some of them might be the real line-outs.
+ */
+ if (!cfg->line_outs && cfg->hp_outs > 1 &&
+ !(cond_flags & HDA_PINCFG_NO_HP_FIXUP)) {
+ i = 0;
+ while (i < cfg->hp_outs) {
+ /* The real HPs should have the sequence 0x0f */
+ if ((hp_out[i].seq & 0x0f) == 0x0f) {
+ i++;
+ continue;
+ }
+ /* Move it to the line-out table */
+ line_out[cfg->line_outs++] = hp_out[i];
+ cfg->hp_outs--;
+ memmove(hp_out + i, hp_out + i + 1,
+ sizeof(hp_out[0]) * (cfg->hp_outs - i));
+ }
+ memset(hp_out + cfg->hp_outs, 0,
+ sizeof(hp_out[0]) * (AUTO_CFG_MAX_OUTS - cfg->hp_outs));
+ if (!cfg->hp_outs)
+ cfg->line_out_type = AUTO_PIN_HP_OUT;
+
+ }
+
+ /* sort by sequence */
+ sort_pins_by_sequence(cfg->line_out_pins, line_out, cfg->line_outs);
+ sort_pins_by_sequence(cfg->speaker_pins, speaker_out,
+ cfg->speaker_outs);
+ sort_pins_by_sequence(cfg->hp_pins, hp_out, cfg->hp_outs);
+
+ /*
+ * FIX-UP: if no line-outs are detected, try to use speaker or HP pin
+ * as a primary output
+ */
+ if (!cfg->line_outs &&
+ !(cond_flags & HDA_PINCFG_NO_LO_FIXUP)) {
+ if (cfg->speaker_outs) {
+ cfg->line_outs = cfg->speaker_outs;
+ memcpy(cfg->line_out_pins, cfg->speaker_pins,
+ sizeof(cfg->speaker_pins));
+ cfg->speaker_outs = 0;
+ memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins));
+ cfg->line_out_type = AUTO_PIN_SPEAKER_OUT;
+ } else if (cfg->hp_outs) {
+ cfg->line_outs = cfg->hp_outs;
+ memcpy(cfg->line_out_pins, cfg->hp_pins,
+ sizeof(cfg->hp_pins));
+ cfg->hp_outs = 0;
+ memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
+ cfg->line_out_type = AUTO_PIN_HP_OUT;
+ }
+ }
+
+ reorder_outputs(cfg->line_outs, cfg->line_out_pins);
+ reorder_outputs(cfg->hp_outs, cfg->hp_pins);
+ reorder_outputs(cfg->speaker_outs, cfg->speaker_pins);
+
+ /* sort inputs in the order of AUTO_PIN_* type */
+ sort(cfg->inputs, cfg->num_inputs, sizeof(cfg->inputs[0]),
+ compare_input_type, NULL);
+
+ /*
+ * debug prints of the parsed results
+ */
+ codec_info(codec, "autoconfig for %s: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x) type:%s\n",
+ codec->core.chip_name, cfg->line_outs, cfg->line_out_pins[0],
+ cfg->line_out_pins[1], cfg->line_out_pins[2],
+ cfg->line_out_pins[3], cfg->line_out_pins[4],
+ cfg->line_out_type == AUTO_PIN_HP_OUT ? "hp" :
+ (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT ?
+ "speaker" : "line"));
+ codec_info(codec, " speaker_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+ cfg->speaker_outs, cfg->speaker_pins[0],
+ cfg->speaker_pins[1], cfg->speaker_pins[2],
+ cfg->speaker_pins[3], cfg->speaker_pins[4]);
+ codec_info(codec, " hp_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+ cfg->hp_outs, cfg->hp_pins[0],
+ cfg->hp_pins[1], cfg->hp_pins[2],
+ cfg->hp_pins[3], cfg->hp_pins[4]);
+ codec_info(codec, " mono: mono_out=0x%x\n", cfg->mono_out_pin);
+ if (cfg->dig_outs)
+ codec_info(codec, " dig-out=0x%x/0x%x\n",
+ cfg->dig_out_pins[0], cfg->dig_out_pins[1]);
+ codec_info(codec, " inputs:\n");
+ for (i = 0; i < cfg->num_inputs; i++) {
+ codec_info(codec, " %s=0x%x\n",
+ hda_get_autocfg_input_label(codec, cfg, i),
+ cfg->inputs[i].pin);
+ }
+ if (cfg->dig_in_pin)
+ codec_info(codec, " dig-in=0x%x\n", cfg->dig_in_pin);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_parse_pin_defcfg);
+
+/**
+ * snd_hda_get_input_pin_attr - Get the input pin attribute from pin config
+ * @def_conf: pin configuration value
+ *
+ * Guess the input pin attribute (INPUT_PIN_ATTR_XXX) from the given
+ * default pin configuration value.
+ */
+int snd_hda_get_input_pin_attr(unsigned int def_conf)
+{
+ unsigned int loc = get_defcfg_location(def_conf);
+ unsigned int conn = get_defcfg_connect(def_conf);
+ if (conn == AC_JACK_PORT_NONE)
+ return INPUT_PIN_ATTR_UNUSED;
+ /* Windows may claim the internal mic to be BOTH, too */
+ if (conn == AC_JACK_PORT_FIXED || conn == AC_JACK_PORT_BOTH)
+ return INPUT_PIN_ATTR_INT;
+ if ((loc & 0x30) == AC_JACK_LOC_INTERNAL)
+ return INPUT_PIN_ATTR_INT;
+ if ((loc & 0x30) == AC_JACK_LOC_SEPARATE)
+ return INPUT_PIN_ATTR_DOCK;
+ if (loc == AC_JACK_LOC_REAR)
+ return INPUT_PIN_ATTR_REAR;
+ if (loc == AC_JACK_LOC_FRONT)
+ return INPUT_PIN_ATTR_FRONT;
+ return INPUT_PIN_ATTR_NORMAL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_input_pin_attr);
+
+/**
+ * hda_get_input_pin_label - Give a label for the given input pin
+ * @codec: the HDA codec
+ * @item: ping config item to refer
+ * @pin: the pin NID
+ * @check_location: flag to add the jack location prefix
+ *
+ * When @check_location is true, the function checks the pin location
+ * for mic and line-in pins, and set an appropriate prefix like "Front",
+ * "Rear", "Internal".
+ */
+static const char *hda_get_input_pin_label(struct hda_codec *codec,
+ const struct auto_pin_cfg_item *item,
+ hda_nid_t pin, bool check_location)
+{
+ unsigned int def_conf;
+ static const char * const mic_names[] = {
+ "Internal Mic", "Dock Mic", "Mic", "Rear Mic", "Front Mic"
+ };
+ int attr;
+
+ def_conf = snd_hda_codec_get_pincfg(codec, pin);
+
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_MIC_IN:
+ if (item && item->is_headset_mic)
+ return "Headset Mic";
+ if (item && item->is_headphone_mic)
+ return "Headphone Mic";
+ if (!check_location)
+ return "Mic";
+ attr = snd_hda_get_input_pin_attr(def_conf);
+ if (!attr)
+ return "None";
+ return mic_names[attr - 1];
+ case AC_JACK_LINE_IN:
+ if (!check_location)
+ return "Line";
+ attr = snd_hda_get_input_pin_attr(def_conf);
+ if (!attr)
+ return "None";
+ if (attr == INPUT_PIN_ATTR_DOCK)
+ return "Dock Line";
+ return "Line";
+ case AC_JACK_AUX:
+ return "Aux";
+ case AC_JACK_CD:
+ return "CD";
+ case AC_JACK_SPDIF_IN:
+ return "SPDIF In";
+ case AC_JACK_DIG_OTHER_IN:
+ return "Digital In";
+ case AC_JACK_HP_OUT:
+ return "Headphone Mic";
+ default:
+ return "Misc";
+ }
+}
+
+/* Check whether the location prefix needs to be added to the label.
+ * If all mic-jacks are in the same location (e.g. rear panel), we don't
+ * have to put "Front" prefix to each label. In such a case, returns false.
+ */
+static int check_mic_location_need(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg,
+ int input)
+{
+ unsigned int defc;
+ int i, attr, attr2;
+
+ defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[input].pin);
+ attr = snd_hda_get_input_pin_attr(defc);
+ /* for internal or docking mics, we need locations */
+ if (attr <= INPUT_PIN_ATTR_NORMAL)
+ return 1;
+
+ attr = 0;
+ for (i = 0; i < cfg->num_inputs; i++) {
+ defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[i].pin);
+ attr2 = snd_hda_get_input_pin_attr(defc);
+ if (attr2 >= INPUT_PIN_ATTR_NORMAL) {
+ if (attr && attr != attr2)
+ return 1; /* different locations found */
+ attr = attr2;
+ }
+ }
+ return 0;
+}
+
+/**
+ * hda_get_autocfg_input_label - Get a label for the given input
+ * @codec: the HDA codec
+ * @cfg: the parsed pin configuration
+ * @input: the input index number
+ *
+ * Get a label for the given input pin defined by the autocfg item.
+ * Unlike hda_get_input_pin_label(), this function checks all inputs
+ * defined in autocfg and avoids the redundant mic/line prefix as much as
+ * possible.
+ */
+const char *hda_get_autocfg_input_label(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg,
+ int input)
+{
+ int type = cfg->inputs[input].type;
+ int has_multiple_pins = 0;
+
+ if ((input > 0 && cfg->inputs[input - 1].type == type) ||
+ (input < cfg->num_inputs - 1 && cfg->inputs[input + 1].type == type))
+ has_multiple_pins = 1;
+ if (has_multiple_pins && type == AUTO_PIN_MIC)
+ has_multiple_pins &= check_mic_location_need(codec, cfg, input);
+ has_multiple_pins |= codec->force_pin_prefix;
+ return hda_get_input_pin_label(codec, &cfg->inputs[input],
+ cfg->inputs[input].pin,
+ has_multiple_pins);
+}
+EXPORT_SYMBOL_GPL(hda_get_autocfg_input_label);
+
+/* return the position of NID in the list, or -1 if not found */
+static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
+{
+ int i;
+ for (i = 0; i < nums; i++)
+ if (list[i] == nid)
+ return i;
+ return -1;
+}
+
+/* get a unique suffix or an index number */
+static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins,
+ int num_pins, int *indexp)
+{
+ static const char * const channel_sfx[] = {
+ " Front", " Surround", " CLFE", " Side"
+ };
+ int i;
+
+ i = find_idx_in_nid_list(nid, pins, num_pins);
+ if (i < 0)
+ return NULL;
+ if (num_pins == 1)
+ return "";
+ if (num_pins > ARRAY_SIZE(channel_sfx)) {
+ if (indexp)
+ *indexp = i;
+ return "";
+ }
+ return channel_sfx[i];
+}
+
+static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ int attr = snd_hda_get_input_pin_attr(def_conf);
+
+ /* check the location */
+ switch (attr) {
+ case INPUT_PIN_ATTR_DOCK:
+ return "Dock ";
+ case INPUT_PIN_ATTR_FRONT:
+ return "Front ";
+ }
+ return "";
+}
+
+static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid,
+ const hda_nid_t *pins, int num_pins)
+{
+ int i, j, idx = 0;
+
+ const char *pfx = check_output_pfx(codec, nid);
+
+ i = find_idx_in_nid_list(nid, pins, num_pins);
+ if (i < 0)
+ return -1;
+ for (j = 0; j < i; j++)
+ if (pfx == check_output_pfx(codec, pins[j]))
+ idx++;
+
+ return idx;
+}
+
+static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ const char *name, char *label, int maxlen,
+ int *indexp)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ int attr = snd_hda_get_input_pin_attr(def_conf);
+ const char *pfx, *sfx = "";
+
+ /* handle as a speaker if it's a fixed line-out */
+ if (!strcmp(name, "Line Out") && attr == INPUT_PIN_ATTR_INT)
+ name = "Speaker";
+ pfx = check_output_pfx(codec, nid);
+
+ if (cfg) {
+ /* try to give a unique suffix if needed */
+ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs,
+ indexp);
+ if (!sfx)
+ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs,
+ indexp);
+ if (!sfx) {
+ /* don't add channel suffix for Headphone controls */
+ int idx = get_hp_label_index(codec, nid, cfg->hp_pins,
+ cfg->hp_outs);
+ if (idx >= 0 && indexp)
+ *indexp = idx;
+ sfx = "";
+ }
+ }
+ snprintf(label, maxlen, "%s%s%s", pfx, name, sfx);
+ return 1;
+}
+
+#define is_hdmi_cfg(conf) \
+ (get_defcfg_location(conf) == AC_JACK_LOC_HDMI)
+
+/**
+ * snd_hda_get_pin_label - Get a label for the given I/O pin
+ * @codec: the HDA codec
+ * @nid: pin NID
+ * @cfg: the parsed pin configuration
+ * @label: the string buffer to store
+ * @maxlen: the max length of string buffer (including termination)
+ * @indexp: the pointer to return the index number (for multiple ctls)
+ *
+ * Get a label for the given pin. This function works for both input and
+ * output pins. When @cfg is given as non-NULL, the function tries to get
+ * an optimized label using hda_get_autocfg_input_label().
+ *
+ * This function tries to give a unique label string for the pin as much as
+ * possible. For example, when the multiple line-outs are present, it adds
+ * the channel suffix like "Front", "Surround", etc (only when @cfg is given).
+ * If no unique name with a suffix is available and @indexp is non-NULL, the
+ * index number is stored in the pointer.
+ */
+int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ char *label, int maxlen, int *indexp)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ const char *name = NULL;
+ int i;
+ bool hdmi;
+
+ if (indexp)
+ *indexp = 0;
+ if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+ return 0;
+
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_LINE_OUT:
+ return fill_audio_out_name(codec, nid, cfg, "Line Out",
+ label, maxlen, indexp);
+ case AC_JACK_SPEAKER:
+ return fill_audio_out_name(codec, nid, cfg, "Speaker",
+ label, maxlen, indexp);
+ case AC_JACK_HP_OUT:
+ return fill_audio_out_name(codec, nid, cfg, "Headphone",
+ label, maxlen, indexp);
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ hdmi = is_hdmi_cfg(def_conf);
+ name = hdmi ? "HDMI" : "SPDIF";
+ if (cfg && indexp)
+ for (i = 0; i < cfg->dig_outs; i++) {
+ hda_nid_t pin = cfg->dig_out_pins[i];
+ unsigned int c;
+ if (pin == nid)
+ break;
+ c = snd_hda_codec_get_pincfg(codec, pin);
+ if (hdmi == is_hdmi_cfg(c))
+ (*indexp)++;
+ }
+ break;
+ default:
+ if (cfg) {
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].pin != nid)
+ continue;
+ name = hda_get_autocfg_input_label(codec, cfg, i);
+ if (name)
+ break;
+ }
+ }
+ if (!name)
+ name = hda_get_input_pin_label(codec, NULL, nid, true);
+ break;
+ }
+ if (!name)
+ return 0;
+ strlcpy(label, name, maxlen);
+ return 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_pin_label);
+
+/**
+ * snd_hda_add_verbs - Add verbs to the init list
+ * @codec: the HDA codec
+ * @list: zero-terminated verb list to add
+ *
+ * Append the given verb list to the execution list. The verbs will be
+ * performed at init and resume time via snd_hda_apply_verbs().
+ */
+int snd_hda_add_verbs(struct hda_codec *codec,
+ const struct hda_verb *list)
+{
+ const struct hda_verb **v;
+ v = snd_array_new(&codec->verbs);
+ if (!v)
+ return -ENOMEM;
+ *v = list;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_verbs);
+
+/**
+ * snd_hda_apply_verbs - Execute the init verb lists
+ * @codec: the HDA codec
+ */
+void snd_hda_apply_verbs(struct hda_codec *codec)
+{
+ const struct hda_verb **v;
+ int i;
+
+ snd_array_for_each(&codec->verbs, i, v)
+ snd_hda_sequence_write(codec, *v);
+}
+EXPORT_SYMBOL_GPL(snd_hda_apply_verbs);
+
+/**
+ * snd_hda_apply_pincfgs - Set each pin config in the given list
+ * @codec: the HDA codec
+ * @cfg: NULL-terminated pin config table
+ */
+void snd_hda_apply_pincfgs(struct hda_codec *codec,
+ const struct hda_pintbl *cfg)
+{
+ for (; cfg->nid; cfg++)
+ snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val);
+}
+EXPORT_SYMBOL_GPL(snd_hda_apply_pincfgs);
+
+static void set_pin_targets(struct hda_codec *codec,
+ const struct hda_pintbl *cfg)
+{
+ for (; cfg->nid; cfg++)
+ snd_hda_set_pin_ctl_cache(codec, cfg->nid, cfg->val);
+}
+
+void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth)
+{
+ const char *modelname = codec->fixup_name;
+
+ while (id >= 0) {
+ const struct hda_fixup *fix = codec->fixup_list + id;
+
+ if (++depth > 10)
+ break;
+ if (fix->chained_before)
+ __snd_hda_apply_fixup(codec, fix->chain_id, action, depth + 1);
+
+ switch (fix->type) {
+ case HDA_FIXUP_PINS:
+ if (action != HDA_FIXUP_ACT_PRE_PROBE || !fix->v.pins)
+ break;
+ codec_dbg(codec, "%s: Apply pincfg for %s\n",
+ codec->core.chip_name, modelname);
+ snd_hda_apply_pincfgs(codec, fix->v.pins);
+ break;
+ case HDA_FIXUP_VERBS:
+ if (action != HDA_FIXUP_ACT_PROBE || !fix->v.verbs)
+ break;
+ codec_dbg(codec, "%s: Apply fix-verbs for %s\n",
+ codec->core.chip_name, modelname);
+ snd_hda_add_verbs(codec, fix->v.verbs);
+ break;
+ case HDA_FIXUP_FUNC:
+ if (!fix->v.func)
+ break;
+ codec_dbg(codec, "%s: Apply fix-func for %s\n",
+ codec->core.chip_name, modelname);
+ fix->v.func(codec, fix, action);
+ break;
+ case HDA_FIXUP_PINCTLS:
+ if (action != HDA_FIXUP_ACT_PROBE || !fix->v.pins)
+ break;
+ codec_dbg(codec, "%s: Apply pinctl for %s\n",
+ codec->core.chip_name, modelname);
+ set_pin_targets(codec, fix->v.pins);
+ break;
+ default:
+ codec_err(codec, "%s: Invalid fixup type %d\n",
+ codec->core.chip_name, fix->type);
+ break;
+ }
+ if (!fix->chained || fix->chained_before)
+ break;
+ id = fix->chain_id;
+ }
+}
+EXPORT_SYMBOL_GPL(__snd_hda_apply_fixup);
+
+/**
+ * snd_hda_apply_fixup - Apply the fixup chain with the given action
+ * @codec: the HDA codec
+ * @action: fixup action (HDA_FIXUP_ACT_XXX)
+ */
+void snd_hda_apply_fixup(struct hda_codec *codec, int action)
+{
+ if (codec->fixup_list)
+ __snd_hda_apply_fixup(codec, codec->fixup_id, action, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hda_apply_fixup);
+
+#define IGNORE_SEQ_ASSOC (~(AC_DEFCFG_SEQUENCE | AC_DEFCFG_DEF_ASSOC))
+
+static bool pin_config_match(struct hda_codec *codec,
+ const struct hda_pintbl *pins,
+ bool match_all_pins)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ hda_nid_t nid = pin->nid;
+ u32 cfg = pin->cfg;
+ const struct hda_pintbl *t_pins;
+ int found;
+
+ t_pins = pins;
+ found = 0;
+ for (; t_pins->nid; t_pins++) {
+ if (t_pins->nid == nid) {
+ found = 1;
+ if ((t_pins->val & IGNORE_SEQ_ASSOC) == (cfg & IGNORE_SEQ_ASSOC))
+ break;
+ else if ((cfg & 0xf0000000) == 0x40000000 && (t_pins->val & 0xf0000000) == 0x40000000)
+ break;
+ else
+ return false;
+ }
+ }
+ if (match_all_pins &&
+ !found && (cfg & 0xf0000000) != 0x40000000)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * snd_hda_pick_pin_fixup - Pick up a fixup matching with the pin quirk list
+ * @codec: the HDA codec
+ * @pin_quirk: zero-terminated pin quirk list
+ * @fixlist: the fixup list
+ * @match_all_pins: all valid pins must match with the table entries
+ */
+void snd_hda_pick_pin_fixup(struct hda_codec *codec,
+ const struct snd_hda_pin_quirk *pin_quirk,
+ const struct hda_fixup *fixlist,
+ bool match_all_pins)
+{
+ const struct snd_hda_pin_quirk *pq;
+
+ if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET)
+ return;
+
+ for (pq = pin_quirk; pq->subvendor; pq++) {
+ if ((codec->core.subsystem_id & 0xffff0000) != (pq->subvendor << 16))
+ continue;
+ if (codec->core.vendor_id != pq->codec)
+ continue;
+ if (pin_config_match(codec, pq->pins, match_all_pins)) {
+ codec->fixup_id = pq->value;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ codec->fixup_name = pq->name;
+ codec_dbg(codec, "%s: picked fixup %s (pin match)\n",
+ codec->core.chip_name, codec->fixup_name);
+#endif
+ codec->fixup_list = fixlist;
+ return;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_pick_pin_fixup);
+
+/**
+ * snd_hda_pick_fixup - Pick up a fixup matching with PCI/codec SSID or model string
+ * @codec: the HDA codec
+ * @models: NULL-terminated model string list
+ * @quirk: zero-terminated PCI/codec SSID quirk list
+ * @fixlist: the fixup list
+ *
+ * Pick up a fixup entry matching with the given model string or SSID.
+ * If a fixup was already set beforehand, the function doesn't do anything.
+ * When a special model string "nofixup" is given, also no fixup is applied.
+ *
+ * The function tries to find the matching model name at first, if given.
+ * If nothing matched, try to look up the PCI SSID.
+ * If still nothing matched, try to look up the codec SSID.
+ */
+void snd_hda_pick_fixup(struct hda_codec *codec,
+ const struct hda_model_fixup *models,
+ const struct snd_pci_quirk *quirk,
+ const struct hda_fixup *fixlist)
+{
+ const struct snd_pci_quirk *q;
+ int id = HDA_FIXUP_ID_NOT_SET;
+ const char *name = NULL;
+
+ if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET)
+ return;
+
+ /* when model=nofixup is given, don't pick up any fixups */
+ if (codec->modelname && !strcmp(codec->modelname, "nofixup")) {
+ codec->fixup_list = NULL;
+ codec->fixup_name = NULL;
+ codec->fixup_id = HDA_FIXUP_ID_NO_FIXUP;
+ codec_dbg(codec, "%s: picked no fixup (nofixup specified)\n",
+ codec->core.chip_name);
+ return;
+ }
+
+ if (codec->modelname && models) {
+ while (models->name) {
+ if (!strcmp(codec->modelname, models->name)) {
+ codec->fixup_id = models->id;
+ codec->fixup_name = models->name;
+ codec->fixup_list = fixlist;
+ codec_dbg(codec, "%s: picked fixup %s (model specified)\n",
+ codec->core.chip_name, codec->fixup_name);
+ return;
+ }
+ models++;
+ }
+ }
+ if (quirk) {
+ q = snd_pci_quirk_lookup(codec->bus->pci, quirk);
+ if (q) {
+ id = q->value;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ name = q->name;
+ codec_dbg(codec, "%s: picked fixup %s (PCI SSID%s)\n",
+ codec->core.chip_name, name, q->subdevice_mask ? "" : " - vendor generic");
+#endif
+ }
+ }
+ if (id < 0 && quirk) {
+ for (q = quirk; q->subvendor || q->subdevice; q++) {
+ unsigned int vendorid =
+ q->subdevice | (q->subvendor << 16);
+ unsigned int mask = 0xffff0000 | q->subdevice_mask;
+ if ((codec->core.subsystem_id & mask) == (vendorid & mask)) {
+ id = q->value;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ name = q->name;
+ codec_dbg(codec, "%s: picked fixup %s (codec SSID)\n",
+ codec->core.chip_name, name);
+#endif
+ break;
+ }
+ }
+ }
+
+ codec->fixup_id = id;
+ if (id >= 0) {
+ codec->fixup_list = fixlist;
+ codec->fixup_name = name;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_pick_fixup);
diff --git a/sound/pci/hda/hda_auto_parser.h b/sound/pci/hda/hda_auto_parser.h
new file mode 100644
index 000000000..a22ca0e17
--- /dev/null
+++ b/sound/pci/hda/hda_auto_parser.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * BIOS auto-parser helper functions for HD-audio
+ *
+ * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_AUTO_PARSER_H
+#define __SOUND_HDA_AUTO_PARSER_H
+
+/*
+ * Helper for automatic pin configuration
+ */
+
+enum {
+ AUTO_PIN_MIC,
+ AUTO_PIN_LINE_IN,
+ AUTO_PIN_CD,
+ AUTO_PIN_AUX,
+ AUTO_PIN_LAST
+};
+
+enum {
+ AUTO_PIN_LINE_OUT,
+ AUTO_PIN_SPEAKER_OUT,
+ AUTO_PIN_HP_OUT
+};
+
+#define AUTO_CFG_MAX_OUTS HDA_MAX_OUTS
+#define AUTO_CFG_MAX_INS 8
+
+struct auto_pin_cfg_item {
+ hda_nid_t pin;
+ int type;
+ unsigned int is_headset_mic:1;
+ unsigned int is_headphone_mic:1; /* Mic-only in headphone jack */
+ unsigned int has_boost_on_pin:1;
+};
+
+struct auto_pin_cfg;
+const char *hda_get_autocfg_input_label(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg,
+ int input);
+int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ char *label, int maxlen, int *indexp);
+
+enum {
+ INPUT_PIN_ATTR_UNUSED, /* pin not connected */
+ INPUT_PIN_ATTR_INT, /* internal mic/line-in */
+ INPUT_PIN_ATTR_DOCK, /* docking mic/line-in */
+ INPUT_PIN_ATTR_NORMAL, /* mic/line-in jack */
+ INPUT_PIN_ATTR_REAR, /* mic/line-in jack in rear */
+ INPUT_PIN_ATTR_FRONT, /* mic/line-in jack in front */
+ INPUT_PIN_ATTR_LAST = INPUT_PIN_ATTR_FRONT,
+};
+
+int snd_hda_get_input_pin_attr(unsigned int def_conf);
+
+struct auto_pin_cfg {
+ int line_outs;
+ /* sorted in the order of Front/Surr/CLFE/Side */
+ hda_nid_t line_out_pins[AUTO_CFG_MAX_OUTS];
+ int speaker_outs;
+ hda_nid_t speaker_pins[AUTO_CFG_MAX_OUTS];
+ int hp_outs;
+ int line_out_type; /* AUTO_PIN_XXX_OUT */
+ hda_nid_t hp_pins[AUTO_CFG_MAX_OUTS];
+ int num_inputs;
+ struct auto_pin_cfg_item inputs[AUTO_CFG_MAX_INS];
+ int dig_outs;
+ hda_nid_t dig_out_pins[2];
+ hda_nid_t dig_in_pin;
+ hda_nid_t mono_out_pin;
+ int dig_out_type[2]; /* HDA_PCM_TYPE_XXX */
+ int dig_in_type; /* HDA_PCM_TYPE_XXX */
+};
+
+/* bit-flags for snd_hda_parse_pin_def_config() behavior */
+#define HDA_PINCFG_NO_HP_FIXUP (1 << 0) /* no HP-split */
+#define HDA_PINCFG_NO_LO_FIXUP (1 << 1) /* don't take other outs as LO */
+#define HDA_PINCFG_HEADSET_MIC (1 << 2) /* Try to find headset mic; mark seq number as 0xc to trigger */
+#define HDA_PINCFG_HEADPHONE_MIC (1 << 3) /* Try to find headphone mic; mark seq number as 0xd to trigger */
+
+int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg,
+ const hda_nid_t *ignore_nids,
+ unsigned int cond_flags);
+
+/* older function */
+#define snd_hda_parse_pin_def_config(codec, cfg, ignore) \
+ snd_hda_parse_pin_defcfg(codec, cfg, ignore, 0)
+
+static inline int auto_cfg_hp_outs(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_HP_OUT) ?
+ cfg->line_outs : cfg->hp_outs;
+}
+static inline const hda_nid_t *auto_cfg_hp_pins(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_HP_OUT) ?
+ cfg->line_out_pins : cfg->hp_pins;
+}
+static inline int auto_cfg_speaker_outs(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ?
+ cfg->line_outs : cfg->speaker_outs;
+}
+static inline const hda_nid_t *auto_cfg_speaker_pins(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ?
+ cfg->line_out_pins : cfg->speaker_pins;
+}
+
+#endif /* __SOUND_HDA_AUTO_PARSER_H */
diff --git a/sound/pci/hda/hda_beep.c b/sound/pci/hda/hda_beep.c
new file mode 100644
index 000000000..e63621bcb
--- /dev/null
+++ b/sound/pci/hda/hda_beep.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Digital Beep Input Interface for HD-audio codec
+ *
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Copyright (c) 2008 Embedded Alley Solutions Inc
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include "hda_beep.h"
+#include "hda_local.h"
+
+enum {
+ DIGBEEP_HZ_STEP = 46875, /* 46.875 Hz */
+ DIGBEEP_HZ_MIN = 93750, /* 93.750 Hz */
+ DIGBEEP_HZ_MAX = 12000000, /* 12 KHz */
+};
+
+/* generate or stop tone */
+static void generate_tone(struct hda_beep *beep, int tone)
+{
+ struct hda_codec *codec = beep->codec;
+
+ if (tone && !beep->playing) {
+ snd_hda_power_up(codec);
+ if (beep->power_hook)
+ beep->power_hook(beep, true);
+ beep->playing = 1;
+ }
+ snd_hda_codec_write(codec, beep->nid, 0,
+ AC_VERB_SET_BEEP_CONTROL, tone);
+ if (!tone && beep->playing) {
+ beep->playing = 0;
+ if (beep->power_hook)
+ beep->power_hook(beep, false);
+ snd_hda_power_down(codec);
+ }
+}
+
+static void snd_hda_generate_beep(struct work_struct *work)
+{
+ struct hda_beep *beep =
+ container_of(work, struct hda_beep, beep_work);
+
+ if (beep->enabled)
+ generate_tone(beep, beep->tone);
+}
+
+/* (non-standard) Linear beep tone calculation for IDT/STAC codecs
+ *
+ * The tone frequency of beep generator on IDT/STAC codecs is
+ * defined from the 8bit tone parameter, in Hz,
+ * freq = 48000 * (257 - tone) / 1024
+ * that is from 12kHz to 93.75Hz in steps of 46.875 Hz
+ */
+static int beep_linear_tone(struct hda_beep *beep, int hz)
+{
+ if (hz <= 0)
+ return 0;
+ hz *= 1000; /* fixed point */
+ hz = hz - DIGBEEP_HZ_MIN
+ + DIGBEEP_HZ_STEP / 2; /* round to nearest step */
+ if (hz < 0)
+ hz = 0; /* turn off PC beep*/
+ else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN))
+ hz = 1; /* max frequency */
+ else {
+ hz /= DIGBEEP_HZ_STEP;
+ hz = 255 - hz;
+ }
+ return hz;
+}
+
+/* HD-audio standard beep tone parameter calculation
+ *
+ * The tone frequency in Hz is calculated as
+ * freq = 48000 / (tone * 4)
+ * from 47Hz to 12kHz
+ */
+static int beep_standard_tone(struct hda_beep *beep, int hz)
+{
+ if (hz <= 0)
+ return 0; /* disabled */
+ hz = 12000 / hz;
+ if (hz > 0xff)
+ return 0xff;
+ if (hz <= 0)
+ return 1;
+ return hz;
+}
+
+static int snd_hda_beep_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int hz)
+{
+ struct hda_beep *beep = input_get_drvdata(dev);
+
+ switch (code) {
+ case SND_BELL:
+ if (hz)
+ hz = 1000;
+ fallthrough;
+ case SND_TONE:
+ if (beep->linear_tone)
+ beep->tone = beep_linear_tone(beep, hz);
+ else
+ beep->tone = beep_standard_tone(beep, hz);
+ break;
+ default:
+ return -1;
+ }
+
+ /* schedule beep event */
+ schedule_work(&beep->beep_work);
+ return 0;
+}
+
+static void turn_on_beep(struct hda_beep *beep)
+{
+ if (beep->keep_power_at_enable)
+ snd_hda_power_up_pm(beep->codec);
+}
+
+static void turn_off_beep(struct hda_beep *beep)
+{
+ cancel_work_sync(&beep->beep_work);
+ if (beep->playing) {
+ /* turn off beep */
+ generate_tone(beep, 0);
+ }
+ if (beep->keep_power_at_enable)
+ snd_hda_power_down_pm(beep->codec);
+}
+
+/**
+ * snd_hda_enable_beep_device - Turn on/off beep sound
+ * @codec: the HDA codec
+ * @enable: flag to turn on/off
+ */
+int snd_hda_enable_beep_device(struct hda_codec *codec, int enable)
+{
+ struct hda_beep *beep = codec->beep;
+ if (!beep)
+ return 0;
+ enable = !!enable;
+ if (beep->enabled != enable) {
+ beep->enabled = enable;
+ if (enable)
+ turn_on_beep(beep);
+ else
+ turn_off_beep(beep);
+ return 1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_enable_beep_device);
+
+static int beep_dev_register(struct snd_device *device)
+{
+ struct hda_beep *beep = device->device_data;
+ int err;
+
+ err = input_register_device(beep->dev);
+ if (!err)
+ beep->registered = true;
+ return err;
+}
+
+static int beep_dev_disconnect(struct snd_device *device)
+{
+ struct hda_beep *beep = device->device_data;
+
+ if (beep->registered)
+ input_unregister_device(beep->dev);
+ else
+ input_free_device(beep->dev);
+ if (beep->enabled)
+ turn_off_beep(beep);
+ return 0;
+}
+
+static int beep_dev_free(struct snd_device *device)
+{
+ struct hda_beep *beep = device->device_data;
+
+ beep->codec->beep = NULL;
+ kfree(beep);
+ return 0;
+}
+
+/**
+ * snd_hda_attach_beep_device - Attach a beep input device
+ * @codec: the HDA codec
+ * @nid: beep NID
+ *
+ * Attach a beep object to the given widget. If beep hint is turned off
+ * explicitly or beep_mode of the codec is turned off, this doesn't nothing.
+ *
+ * Currently, only one beep device is allowed to each codec.
+ */
+int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
+{
+ static const struct snd_device_ops ops = {
+ .dev_register = beep_dev_register,
+ .dev_disconnect = beep_dev_disconnect,
+ .dev_free = beep_dev_free,
+ };
+ struct input_dev *input_dev;
+ struct hda_beep *beep;
+ int err;
+
+ if (!snd_hda_get_bool_hint(codec, "beep"))
+ return 0; /* disabled explicitly by hints */
+ if (codec->beep_mode == HDA_BEEP_MODE_OFF)
+ return 0; /* disabled by module option */
+
+ beep = kzalloc(sizeof(*beep), GFP_KERNEL);
+ if (beep == NULL)
+ return -ENOMEM;
+ snprintf(beep->phys, sizeof(beep->phys),
+ "card%d/codec#%d/beep0", codec->card->number, codec->addr);
+ /* enable linear scale */
+ snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_DIGI_CONVERT_2, 0x01);
+
+ beep->nid = nid;
+ beep->codec = codec;
+ codec->beep = beep;
+
+ INIT_WORK(&beep->beep_work, &snd_hda_generate_beep);
+ mutex_init(&beep->mutex);
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ err = -ENOMEM;
+ goto err_free;
+ }
+
+ /* setup digital beep device */
+ input_dev->name = "HDA Digital PCBeep";
+ input_dev->phys = beep->phys;
+ input_dev->id.bustype = BUS_PCI;
+ input_dev->dev.parent = &codec->card->card_dev;
+
+ input_dev->id.vendor = codec->core.vendor_id >> 16;
+ input_dev->id.product = codec->core.vendor_id & 0xffff;
+ input_dev->id.version = 0x01;
+
+ input_dev->evbit[0] = BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+ input_dev->event = snd_hda_beep_event;
+ input_set_drvdata(input_dev, beep);
+
+ beep->dev = input_dev;
+
+ err = snd_device_new(codec->card, SNDRV_DEV_JACK, beep, &ops);
+ if (err < 0)
+ goto err_input;
+
+ return 0;
+
+ err_input:
+ input_free_device(beep->dev);
+ err_free:
+ kfree(beep);
+ codec->beep = NULL;
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_attach_beep_device);
+
+/**
+ * snd_hda_detach_beep_device - Detach the beep device
+ * @codec: the HDA codec
+ */
+void snd_hda_detach_beep_device(struct hda_codec *codec)
+{
+ if (!codec->bus->shutdown && codec->beep)
+ snd_device_free(codec->card, codec->beep);
+}
+EXPORT_SYMBOL_GPL(snd_hda_detach_beep_device);
+
+static bool ctl_has_mute(struct snd_kcontrol *kcontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ return query_amp_caps(codec, get_amp_nid(kcontrol),
+ get_amp_direction(kcontrol)) & AC_AMPCAP_MUTE;
+}
+
+/* get/put callbacks for beep mute mixer switches */
+
+/**
+ * snd_hda_mixer_amp_switch_get_beep - Get callback for beep controls
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ */
+int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_beep *beep = codec->beep;
+ int chs = get_amp_channels(kcontrol);
+
+ if (beep && (!beep->enabled || !ctl_has_mute(kcontrol))) {
+ if (chs & 1)
+ ucontrol->value.integer.value[0] = beep->enabled;
+ if (chs & 2)
+ ucontrol->value.integer.value[1] = beep->enabled;
+ return 0;
+ }
+ return snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get_beep);
+
+/**
+ * snd_hda_mixer_amp_switch_put_beep - Put callback for beep controls
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ */
+int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_beep *beep = codec->beep;
+ if (beep) {
+ u8 chs = get_amp_channels(kcontrol);
+ int enable = 0;
+ long *valp = ucontrol->value.integer.value;
+ if (chs & 1) {
+ enable |= *valp;
+ valp++;
+ }
+ if (chs & 2)
+ enable |= *valp;
+ snd_hda_enable_beep_device(codec, enable);
+ }
+ if (!ctl_has_mute(kcontrol))
+ return 0;
+ return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put_beep);
diff --git a/sound/pci/hda/hda_beep.h b/sound/pci/hda/hda_beep.h
new file mode 100644
index 000000000..db76e3ddb
--- /dev/null
+++ b/sound/pci/hda/hda_beep.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Digital Beep Input Interface for HD-audio codec
+ *
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Copyright (c) 2008 Embedded Alley Solutions Inc
+ */
+
+#ifndef __SOUND_HDA_BEEP_H
+#define __SOUND_HDA_BEEP_H
+
+#include <sound/hda_codec.h>
+
+#define HDA_BEEP_MODE_OFF 0
+#define HDA_BEEP_MODE_ON 1
+
+/* beep information */
+struct hda_beep {
+ struct input_dev *dev;
+ struct hda_codec *codec;
+ char phys[32];
+ int tone;
+ hda_nid_t nid;
+ unsigned int registered:1;
+ unsigned int enabled:1;
+ unsigned int linear_tone:1; /* linear tone for IDT/STAC codec */
+ unsigned int playing:1;
+ unsigned int keep_power_at_enable:1; /* set by driver */
+ struct work_struct beep_work; /* scheduled task for beep event */
+ struct mutex mutex;
+ void (*power_hook)(struct hda_beep *beep, bool on);
+};
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+int snd_hda_enable_beep_device(struct hda_codec *codec, int enable);
+int snd_hda_attach_beep_device(struct hda_codec *codec, int nid);
+void snd_hda_detach_beep_device(struct hda_codec *codec);
+#else
+static inline int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
+{
+ return 0;
+}
+static inline void snd_hda_detach_beep_device(struct hda_codec *codec)
+{
+}
+#endif
+#endif
diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c
new file mode 100644
index 000000000..0a83afa5f
--- /dev/null
+++ b/sound/pci/hda/hda_bind.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio codec driver binding
+ * Copyright (c) Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+
+/*
+ * find a matching codec id
+ */
+static int hda_codec_match(struct hdac_device *dev, struct hdac_driver *drv)
+{
+ struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+ struct hda_codec_driver *driver =
+ container_of(drv, struct hda_codec_driver, core);
+ const struct hda_device_id *list;
+ /* check probe_id instead of vendor_id if set */
+ u32 id = codec->probe_id ? codec->probe_id : codec->core.vendor_id;
+ u32 rev_id = codec->core.revision_id;
+
+ for (list = driver->id; list->vendor_id; list++) {
+ if (list->vendor_id == id &&
+ (!list->rev_id || list->rev_id == rev_id)) {
+ codec->preset = list;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* process an unsolicited event */
+static void hda_codec_unsol_event(struct hdac_device *dev, unsigned int ev)
+{
+ struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+
+ /* ignore unsol events during shutdown */
+ if (codec->bus->shutdown)
+ return;
+
+ /* ignore unsol events during system suspend/resume */
+ if (codec->core.dev.power.power_state.event != PM_EVENT_ON)
+ return;
+
+ if (codec->patch_ops.unsol_event)
+ codec->patch_ops.unsol_event(codec, ev);
+}
+
+/**
+ * snd_hda_codec_set_name - set the codec name
+ * @codec: the HDA codec
+ * @name: name string to set
+ */
+int snd_hda_codec_set_name(struct hda_codec *codec, const char *name)
+{
+ int err;
+
+ if (!name)
+ return 0;
+ err = snd_hdac_device_set_chip_name(&codec->core, name);
+ if (err < 0)
+ return err;
+
+ /* update the mixer name */
+ if (!*codec->card->mixername ||
+ codec->bus->mixer_assigned >= codec->core.addr) {
+ snprintf(codec->card->mixername,
+ sizeof(codec->card->mixername), "%s %s",
+ codec->core.vendor_name, codec->core.chip_name);
+ codec->bus->mixer_assigned = codec->core.addr;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_name);
+
+static int hda_codec_driver_probe(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+ struct module *owner = dev->driver->owner;
+ hda_codec_patch_t patch;
+ int err;
+
+ if (codec->bus->core.ext_ops) {
+ if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach))
+ return -EINVAL;
+ return codec->bus->core.ext_ops->hdev_attach(&codec->core);
+ }
+
+ if (WARN_ON(!codec->preset))
+ return -EINVAL;
+
+ err = snd_hda_codec_set_name(codec, codec->preset->name);
+ if (err < 0)
+ goto error;
+ err = snd_hdac_regmap_init(&codec->core);
+ if (err < 0)
+ goto error;
+
+ if (!try_module_get(owner)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ patch = (hda_codec_patch_t)codec->preset->driver_data;
+ if (patch) {
+ err = patch(codec);
+ if (err < 0)
+ goto error_module_put;
+ }
+
+ err = snd_hda_codec_build_pcms(codec);
+ if (err < 0)
+ goto error_module;
+ err = snd_hda_codec_build_controls(codec);
+ if (err < 0)
+ goto error_module;
+ /* only register after the bus probe finished; otherwise it's racy */
+ if (!codec->bus->bus_probing && codec->card->registered) {
+ err = snd_card_register(codec->card);
+ if (err < 0)
+ goto error_module;
+ snd_hda_codec_register(codec);
+ }
+
+ codec->core.lazy_cache = true;
+ return 0;
+
+ error_module:
+ if (codec->patch_ops.free)
+ codec->patch_ops.free(codec);
+ error_module_put:
+ module_put(owner);
+
+ error:
+ snd_hda_codec_cleanup_for_unbind(codec);
+ codec->preset = NULL;
+ return err;
+}
+
+static int hda_codec_driver_remove(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ if (codec->bus->core.ext_ops) {
+ if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach))
+ return -EINVAL;
+ return codec->bus->core.ext_ops->hdev_detach(&codec->core);
+ }
+
+ if (codec->patch_ops.free)
+ codec->patch_ops.free(codec);
+ snd_hda_codec_cleanup_for_unbind(codec);
+ codec->preset = NULL;
+ module_put(dev->driver->owner);
+ return 0;
+}
+
+static void hda_codec_driver_shutdown(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ if (!pm_runtime_suspended(dev) && codec->patch_ops.reboot_notify)
+ codec->patch_ops.reboot_notify(codec);
+}
+
+int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name,
+ struct module *owner)
+{
+ drv->core.driver.name = name;
+ drv->core.driver.owner = owner;
+ drv->core.driver.bus = &snd_hda_bus_type;
+ drv->core.driver.probe = hda_codec_driver_probe;
+ drv->core.driver.remove = hda_codec_driver_remove;
+ drv->core.driver.shutdown = hda_codec_driver_shutdown;
+ drv->core.driver.pm = &hda_codec_driver_pm;
+ drv->core.type = HDA_DEV_LEGACY;
+ drv->core.match = hda_codec_match;
+ drv->core.unsol_event = hda_codec_unsol_event;
+ return driver_register(&drv->core.driver);
+}
+EXPORT_SYMBOL_GPL(__hda_codec_driver_register);
+
+void hda_codec_driver_unregister(struct hda_codec_driver *drv)
+{
+ driver_unregister(&drv->core.driver);
+}
+EXPORT_SYMBOL_GPL(hda_codec_driver_unregister);
+
+static inline bool codec_probed(struct hda_codec *codec)
+{
+ return device_attach(hda_codec_dev(codec)) > 0 && codec->preset;
+}
+
+/* try to auto-load codec module */
+static void request_codec_module(struct hda_codec *codec)
+{
+#ifdef MODULE
+ char modalias[32];
+ const char *mod = NULL;
+
+ switch (codec->probe_id) {
+ case HDA_CODEC_ID_GENERIC_HDMI:
+#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI)
+ mod = "snd-hda-codec-hdmi";
+#endif
+ break;
+ case HDA_CODEC_ID_GENERIC:
+#if IS_MODULE(CONFIG_SND_HDA_GENERIC)
+ mod = "snd-hda-codec-generic";
+#endif
+ break;
+ default:
+ snd_hdac_codec_modalias(&codec->core, modalias, sizeof(modalias));
+ mod = modalias;
+ break;
+ }
+
+ if (mod)
+ request_module(mod);
+#endif /* MODULE */
+}
+
+/* try to auto-load and bind the codec module */
+static void codec_bind_module(struct hda_codec *codec)
+{
+#ifdef MODULE
+ request_codec_module(codec);
+ if (codec_probed(codec))
+ return;
+#endif
+}
+
+#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
+/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */
+static bool is_likely_hdmi_codec(struct hda_codec *codec)
+{
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, nid);
+ switch (get_wcaps_type(wcaps)) {
+ case AC_WID_AUD_IN:
+ return false; /* HDMI parser supports only HDMI out */
+ case AC_WID_AUD_OUT:
+ if (!(wcaps & AC_WCAP_DIGITAL))
+ return false;
+ break;
+ }
+ }
+ return true;
+}
+#else
+/* no HDMI codec parser support */
+#define is_likely_hdmi_codec(codec) false
+#endif /* CONFIG_SND_HDA_CODEC_HDMI */
+
+static int codec_bind_generic(struct hda_codec *codec)
+{
+ if (codec->probe_id)
+ return -ENODEV;
+
+ if (is_likely_hdmi_codec(codec)) {
+ codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI;
+ request_codec_module(codec);
+ if (codec_probed(codec))
+ return 0;
+ }
+
+ codec->probe_id = HDA_CODEC_ID_GENERIC;
+ request_codec_module(codec);
+ if (codec_probed(codec))
+ return 0;
+ return -ENODEV;
+}
+
+#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
+#define is_generic_config(codec) \
+ (codec->modelname && !strcmp(codec->modelname, "generic"))
+#else
+#define is_generic_config(codec) 0
+#endif
+
+/**
+ * snd_hda_codec_configure - (Re-)configure the HD-audio codec
+ * @codec: the HDA codec
+ *
+ * Start parsing of the given codec tree and (re-)initialize the whole
+ * patch instance.
+ *
+ * Returns 0 if successful or a negative error code.
+ */
+int snd_hda_codec_configure(struct hda_codec *codec)
+{
+ int err;
+
+ if (codec->configured)
+ return 0;
+
+ if (is_generic_config(codec))
+ codec->probe_id = HDA_CODEC_ID_GENERIC;
+ else
+ codec->probe_id = 0;
+
+ if (!device_is_registered(&codec->core.dev)) {
+ err = snd_hdac_device_register(&codec->core);
+ if (err < 0)
+ return err;
+ }
+
+ if (!codec->preset)
+ codec_bind_module(codec);
+ if (!codec->preset) {
+ err = codec_bind_generic(codec);
+ if (err < 0) {
+ codec_dbg(codec, "Unable to bind the codec\n");
+ return err;
+ }
+ }
+
+ codec->configured = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_configure);
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
new file mode 100644
index 000000000..fc4a64a83
--- /dev/null
+++ b/sound/pci/hda/hda_codec.c
@@ -0,0 +1,4061 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include <sound/asoundef.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+#include "hda_local.h"
+#include "hda_beep.h"
+#include "hda_jack.h"
+#include <sound/hda_hwdep.h>
+#include <sound/hda_component.h>
+
+#define codec_in_pm(codec) snd_hdac_is_in_pm(&codec->core)
+#define hda_codec_is_power_on(codec) snd_hdac_is_power_on(&codec->core)
+#define codec_has_epss(codec) \
+ ((codec)->core.power_caps & AC_PWRST_EPSS)
+#define codec_has_clkstop(codec) \
+ ((codec)->core.power_caps & AC_PWRST_CLKSTOP)
+
+/*
+ * Send and receive a verb - passed to exec_verb override for hdac_device
+ */
+static int codec_exec_verb(struct hdac_device *dev, unsigned int cmd,
+ unsigned int flags, unsigned int *res)
+{
+ struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+ struct hda_bus *bus = codec->bus;
+ int err;
+
+ if (cmd == ~0)
+ return -1;
+
+ again:
+ snd_hda_power_up_pm(codec);
+ mutex_lock(&bus->core.cmd_mutex);
+ if (flags & HDA_RW_NO_RESPONSE_FALLBACK)
+ bus->no_response_fallback = 1;
+ err = snd_hdac_bus_exec_verb_unlocked(&bus->core, codec->core.addr,
+ cmd, res);
+ bus->no_response_fallback = 0;
+ mutex_unlock(&bus->core.cmd_mutex);
+ snd_hda_power_down_pm(codec);
+ if (!codec_in_pm(codec) && res && err == -EAGAIN) {
+ if (bus->response_reset) {
+ codec_dbg(codec,
+ "resetting BUS due to fatal communication error\n");
+ snd_hda_bus_reset(bus);
+ }
+ goto again;
+ }
+ /* clear reset-flag when the communication gets recovered */
+ if (!err || codec_in_pm(codec))
+ bus->response_reset = 0;
+ return err;
+}
+
+/**
+ * snd_hda_sequence_write - sequence writes
+ * @codec: the HDA codec
+ * @seq: VERB array to send
+ *
+ * Send the commands sequentially from the given array.
+ * The array must be terminated with NID=0.
+ */
+void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq)
+{
+ for (; seq->nid; seq++)
+ snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param);
+}
+EXPORT_SYMBOL_GPL(snd_hda_sequence_write);
+
+/* connection list element */
+struct hda_conn_list {
+ struct list_head list;
+ int len;
+ hda_nid_t nid;
+ hda_nid_t conns[];
+};
+
+/* look up the cached results */
+static struct hda_conn_list *
+lookup_conn_list(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_conn_list *p;
+ list_for_each_entry(p, &codec->conn_list, list) {
+ if (p->nid == nid)
+ return p;
+ }
+ return NULL;
+}
+
+static int add_conn_list(struct hda_codec *codec, hda_nid_t nid, int len,
+ const hda_nid_t *list)
+{
+ struct hda_conn_list *p;
+
+ p = kmalloc(struct_size(p, conns, len), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ p->len = len;
+ p->nid = nid;
+ memcpy(p->conns, list, len * sizeof(hda_nid_t));
+ list_add(&p->list, &codec->conn_list);
+ return 0;
+}
+
+static void remove_conn_list(struct hda_codec *codec)
+{
+ while (!list_empty(&codec->conn_list)) {
+ struct hda_conn_list *p;
+ p = list_first_entry(&codec->conn_list, typeof(*p), list);
+ list_del(&p->list);
+ kfree(p);
+ }
+}
+
+/* read the connection and add to the cache */
+static int read_and_add_raw_conns(struct hda_codec *codec, hda_nid_t nid)
+{
+ hda_nid_t list[32];
+ hda_nid_t *result = list;
+ int len;
+
+ len = snd_hda_get_raw_connections(codec, nid, list, ARRAY_SIZE(list));
+ if (len == -ENOSPC) {
+ len = snd_hda_get_num_raw_conns(codec, nid);
+ result = kmalloc_array(len, sizeof(hda_nid_t), GFP_KERNEL);
+ if (!result)
+ return -ENOMEM;
+ len = snd_hda_get_raw_connections(codec, nid, result, len);
+ }
+ if (len >= 0)
+ len = snd_hda_override_conn_list(codec, nid, len, result);
+ if (result != list)
+ kfree(result);
+ return len;
+}
+
+/**
+ * snd_hda_get_conn_list - get connection list
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @listp: the pointer to store NID list
+ *
+ * Parses the connection list of the given widget and stores the pointer
+ * to the list of NIDs.
+ *
+ * Returns the number of connections, or a negative error code.
+ *
+ * Note that the returned pointer isn't protected against the list
+ * modification. If snd_hda_override_conn_list() might be called
+ * concurrently, protect with a mutex appropriately.
+ */
+int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid,
+ const hda_nid_t **listp)
+{
+ bool added = false;
+
+ for (;;) {
+ int err;
+ const struct hda_conn_list *p;
+
+ /* if the connection-list is already cached, read it */
+ p = lookup_conn_list(codec, nid);
+ if (p) {
+ if (listp)
+ *listp = p->conns;
+ return p->len;
+ }
+ if (snd_BUG_ON(added))
+ return -EINVAL;
+
+ err = read_and_add_raw_conns(codec, nid);
+ if (err < 0)
+ return err;
+ added = true;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_conn_list);
+
+/**
+ * snd_hda_get_connections - copy connection list
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @conn_list: connection list array; when NULL, checks only the size
+ * @max_conns: max. number of connections to store
+ *
+ * Parses the connection list of the given widget and stores the list
+ * of NIDs.
+ *
+ * Returns the number of connections, or a negative error code.
+ */
+int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
+ hda_nid_t *conn_list, int max_conns)
+{
+ const hda_nid_t *list;
+ int len = snd_hda_get_conn_list(codec, nid, &list);
+
+ if (len > 0 && conn_list) {
+ if (len > max_conns) {
+ codec_err(codec, "Too many connections %d for NID 0x%x\n",
+ len, nid);
+ return -EINVAL;
+ }
+ memcpy(conn_list, list, len * sizeof(hda_nid_t));
+ }
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_connections);
+
+/**
+ * snd_hda_override_conn_list - add/modify the connection-list to cache
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @len: number of connection list entries
+ * @list: the list of connection entries
+ *
+ * Add or modify the given connection-list to the cache. If the corresponding
+ * cache already exists, invalidate it and append a new one.
+ *
+ * Returns zero or a negative error code.
+ */
+int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int len,
+ const hda_nid_t *list)
+{
+ struct hda_conn_list *p;
+
+ p = lookup_conn_list(codec, nid);
+ if (p) {
+ list_del(&p->list);
+ kfree(p);
+ }
+
+ return add_conn_list(codec, nid, len, list);
+}
+EXPORT_SYMBOL_GPL(snd_hda_override_conn_list);
+
+/**
+ * snd_hda_get_conn_index - get the connection index of the given NID
+ * @codec: the HDA codec
+ * @mux: NID containing the list
+ * @nid: NID to select
+ * @recursive: 1 when searching NID recursively, otherwise 0
+ *
+ * Parses the connection list of the widget @mux and checks whether the
+ * widget @nid is present. If it is, return the connection index.
+ * Otherwise it returns -1.
+ */
+int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux,
+ hda_nid_t nid, int recursive)
+{
+ const hda_nid_t *conn;
+ int i, nums;
+
+ nums = snd_hda_get_conn_list(codec, mux, &conn);
+ for (i = 0; i < nums; i++)
+ if (conn[i] == nid)
+ return i;
+ if (!recursive)
+ return -1;
+ if (recursive > 10) {
+ codec_dbg(codec, "too deep connection for 0x%x\n", nid);
+ return -1;
+ }
+ recursive++;
+ for (i = 0; i < nums; i++) {
+ unsigned int type = get_wcaps_type(get_wcaps(codec, conn[i]));
+ if (type == AC_WID_PIN || type == AC_WID_AUD_OUT)
+ continue;
+ if (snd_hda_get_conn_index(codec, conn[i], nid, recursive) >= 0)
+ return i;
+ }
+ return -1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_conn_index);
+
+/**
+ * snd_hda_get_num_devices - get DEVLIST_LEN parameter of the given widget
+ * @codec: the HDA codec
+ * @nid: NID of the pin to parse
+ *
+ * Get the device entry number on the given widget. This is a feature of
+ * DP MST audio. Each pin can have several device entries in it.
+ */
+unsigned int snd_hda_get_num_devices(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int parm;
+
+ if (!codec->dp_mst || !(wcaps & AC_WCAP_DIGITAL) ||
+ get_wcaps_type(wcaps) != AC_WID_PIN)
+ return 0;
+
+ parm = snd_hdac_read_parm_uncached(&codec->core, nid, AC_PAR_DEVLIST_LEN);
+ if (parm == -1)
+ parm = 0;
+ return parm & AC_DEV_LIST_LEN_MASK;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_num_devices);
+
+/**
+ * snd_hda_get_devices - copy device list without cache
+ * @codec: the HDA codec
+ * @nid: NID of the pin to parse
+ * @dev_list: device list array
+ * @max_devices: max. number of devices to store
+ *
+ * Copy the device list. This info is dynamic and so not cached.
+ * Currently called only from hda_proc.c, so not exported.
+ */
+int snd_hda_get_devices(struct hda_codec *codec, hda_nid_t nid,
+ u8 *dev_list, int max_devices)
+{
+ unsigned int parm;
+ int i, dev_len, devices;
+
+ parm = snd_hda_get_num_devices(codec, nid);
+ if (!parm) /* not multi-stream capable */
+ return 0;
+
+ dev_len = parm + 1;
+ dev_len = dev_len < max_devices ? dev_len : max_devices;
+
+ devices = 0;
+ while (devices < dev_len) {
+ if (snd_hdac_read(&codec->core, nid,
+ AC_VERB_GET_DEVICE_LIST, devices, &parm))
+ break; /* error */
+
+ for (i = 0; i < 8; i++) {
+ dev_list[devices] = (u8)parm;
+ parm >>= 4;
+ devices++;
+ if (devices >= dev_len)
+ break;
+ }
+ }
+ return devices;
+}
+
+/**
+ * snd_hda_get_dev_select - get device entry select on the pin
+ * @codec: the HDA codec
+ * @nid: NID of the pin to get device entry select
+ *
+ * Get the devcie entry select on the pin. Return the device entry
+ * id selected on the pin. Return 0 means the first device entry
+ * is selected or MST is not supported.
+ */
+int snd_hda_get_dev_select(struct hda_codec *codec, hda_nid_t nid)
+{
+ /* not support dp_mst will always return 0, using first dev_entry */
+ if (!codec->dp_mst)
+ return 0;
+
+ return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DEVICE_SEL, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_dev_select);
+
+/**
+ * snd_hda_set_dev_select - set device entry select on the pin
+ * @codec: the HDA codec
+ * @nid: NID of the pin to set device entry select
+ * @dev_id: device entry id to be set
+ *
+ * Set the device entry select on the pin nid.
+ */
+int snd_hda_set_dev_select(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ int ret, num_devices;
+
+ /* not support dp_mst will always return 0, using first dev_entry */
+ if (!codec->dp_mst)
+ return 0;
+
+ /* AC_PAR_DEVLIST_LEN is 0 based. */
+ num_devices = snd_hda_get_num_devices(codec, nid) + 1;
+ /* If Device List Length is 0 (num_device = 1),
+ * the pin is not multi stream capable.
+ * Do nothing in this case.
+ */
+ if (num_devices == 1)
+ return 0;
+
+ /* Behavior of setting index being equal to or greater than
+ * Device List Length is not predictable
+ */
+ if (num_devices <= dev_id)
+ return -EINVAL;
+
+ ret = snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_DEVICE_SEL, dev_id);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_set_dev_select);
+
+/*
+ * read widget caps for each widget and store in cache
+ */
+static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node)
+{
+ int i;
+ hda_nid_t nid;
+
+ codec->wcaps = kmalloc_array(codec->core.num_nodes, 4, GFP_KERNEL);
+ if (!codec->wcaps)
+ return -ENOMEM;
+ nid = codec->core.start_nid;
+ for (i = 0; i < codec->core.num_nodes; i++, nid++)
+ codec->wcaps[i] = snd_hdac_read_parm_uncached(&codec->core,
+ nid, AC_PAR_AUDIO_WIDGET_CAP);
+ return 0;
+}
+
+/* read all pin default configurations and save codec->init_pins */
+static int read_pin_defaults(struct hda_codec *codec)
+{
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec) {
+ struct hda_pincfg *pin;
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int wid_type = get_wcaps_type(wcaps);
+ if (wid_type != AC_WID_PIN)
+ continue;
+ pin = snd_array_new(&codec->init_pins);
+ if (!pin)
+ return -ENOMEM;
+ pin->nid = nid;
+ pin->cfg = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_CONFIG_DEFAULT, 0);
+ /*
+ * all device entries are the same widget control so far
+ * fixme: if any codec is different, need fix here
+ */
+ pin->ctrl = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL,
+ 0);
+ }
+ return 0;
+}
+
+/* look up the given pin config list and return the item matching with NID */
+static struct hda_pincfg *look_up_pincfg(struct hda_codec *codec,
+ struct snd_array *array,
+ hda_nid_t nid)
+{
+ struct hda_pincfg *pin;
+ int i;
+
+ snd_array_for_each(array, i, pin) {
+ if (pin->nid == nid)
+ return pin;
+ }
+ return NULL;
+}
+
+/* set the current pin config value for the given NID.
+ * the value is cached, and read via snd_hda_codec_get_pincfg()
+ */
+int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list,
+ hda_nid_t nid, unsigned int cfg)
+{
+ struct hda_pincfg *pin;
+
+ /* the check below may be invalid when pins are added by a fixup
+ * dynamically (e.g. via snd_hda_codec_update_widgets()), so disabled
+ * for now
+ */
+ /*
+ if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+ return -EINVAL;
+ */
+
+ pin = look_up_pincfg(codec, list, nid);
+ if (!pin) {
+ pin = snd_array_new(list);
+ if (!pin)
+ return -ENOMEM;
+ pin->nid = nid;
+ }
+ pin->cfg = cfg;
+ return 0;
+}
+
+/**
+ * snd_hda_codec_set_pincfg - Override a pin default configuration
+ * @codec: the HDA codec
+ * @nid: NID to set the pin config
+ * @cfg: the pin default config value
+ *
+ * Override a pin default configuration value in the cache.
+ * This value can be read by snd_hda_codec_get_pincfg() in a higher
+ * priority than the real hardware value.
+ */
+int snd_hda_codec_set_pincfg(struct hda_codec *codec,
+ hda_nid_t nid, unsigned int cfg)
+{
+ return snd_hda_add_pincfg(codec, &codec->driver_pins, nid, cfg);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_pincfg);
+
+/**
+ * snd_hda_codec_get_pincfg - Obtain a pin-default configuration
+ * @codec: the HDA codec
+ * @nid: NID to get the pin config
+ *
+ * Get the current pin config value of the given pin NID.
+ * If the pincfg value is cached or overridden via sysfs or driver,
+ * returns the cached value.
+ */
+unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_pincfg *pin;
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+ {
+ unsigned int cfg = 0;
+ mutex_lock(&codec->user_mutex);
+ pin = look_up_pincfg(codec, &codec->user_pins, nid);
+ if (pin)
+ cfg = pin->cfg;
+ mutex_unlock(&codec->user_mutex);
+ if (cfg)
+ return cfg;
+ }
+#endif
+ pin = look_up_pincfg(codec, &codec->driver_pins, nid);
+ if (pin)
+ return pin->cfg;
+ pin = look_up_pincfg(codec, &codec->init_pins, nid);
+ if (pin)
+ return pin->cfg;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_get_pincfg);
+
+/**
+ * snd_hda_codec_set_pin_target - remember the current pinctl target value
+ * @codec: the HDA codec
+ * @nid: pin NID
+ * @val: assigned pinctl value
+ *
+ * This function stores the given value to a pinctl target value in the
+ * pincfg table. This isn't always as same as the actually written value
+ * but can be referred at any time via snd_hda_codec_get_pin_target().
+ */
+int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int val)
+{
+ struct hda_pincfg *pin;
+
+ pin = look_up_pincfg(codec, &codec->init_pins, nid);
+ if (!pin)
+ return -EINVAL;
+ pin->target = val;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_pin_target);
+
+/**
+ * snd_hda_codec_get_pin_target - return the current pinctl target value
+ * @codec: the HDA codec
+ * @nid: pin NID
+ */
+int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_pincfg *pin;
+
+ pin = look_up_pincfg(codec, &codec->init_pins, nid);
+ if (!pin)
+ return 0;
+ return pin->target;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_get_pin_target);
+
+/**
+ * snd_hda_shutup_pins - Shut up all pins
+ * @codec: the HDA codec
+ *
+ * Clear all pin controls to shup up before suspend for avoiding click noise.
+ * The controls aren't cached so that they can be resumed properly.
+ */
+void snd_hda_shutup_pins(struct hda_codec *codec)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ /* don't shut up pins when unloading the driver; otherwise it breaks
+ * the default pin setup at the next load of the driver
+ */
+ if (codec->bus->shutdown)
+ return;
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ /* use read here for syncing after issuing each verb */
+ snd_hda_codec_read(codec, pin->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+ }
+ codec->pins_shutup = 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_shutup_pins);
+
+#ifdef CONFIG_PM
+/* Restore the pin controls cleared previously via snd_hda_shutup_pins() */
+static void restore_shutup_pins(struct hda_codec *codec)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ if (!codec->pins_shutup)
+ return;
+ if (codec->bus->shutdown)
+ return;
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ snd_hda_codec_write(codec, pin->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ pin->ctrl);
+ }
+ codec->pins_shutup = 0;
+}
+#endif
+
+static void hda_jackpoll_work(struct work_struct *work)
+{
+ struct hda_codec *codec =
+ container_of(work, struct hda_codec, jackpoll_work.work);
+
+ /* for non-polling trigger: we need nothing if already powered on */
+ if (!codec->jackpoll_interval && snd_hdac_is_power_on(&codec->core))
+ return;
+
+ /* the power-up/down sequence triggers the runtime resume */
+ snd_hda_power_up_pm(codec);
+ /* update jacks manually if polling is required, too */
+ if (codec->jackpoll_interval) {
+ snd_hda_jack_set_dirty_all(codec);
+ snd_hda_jack_poll_all(codec);
+ }
+ snd_hda_power_down_pm(codec);
+
+ if (!codec->jackpoll_interval)
+ return;
+
+ schedule_delayed_work(&codec->jackpoll_work,
+ codec->jackpoll_interval);
+}
+
+/* release all pincfg lists */
+static void free_init_pincfgs(struct hda_codec *codec)
+{
+ snd_array_free(&codec->driver_pins);
+#ifdef CONFIG_SND_HDA_RECONFIG
+ snd_array_free(&codec->user_pins);
+#endif
+ snd_array_free(&codec->init_pins);
+}
+
+/*
+ * audio-converter setup caches
+ */
+struct hda_cvt_setup {
+ hda_nid_t nid;
+ u8 stream_tag;
+ u8 channel_id;
+ u16 format_id;
+ unsigned char active; /* cvt is currently used */
+ unsigned char dirty; /* setups should be cleared */
+};
+
+/* get or create a cache entry for the given audio converter NID */
+static struct hda_cvt_setup *
+get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_cvt_setup *p;
+ int i;
+
+ snd_array_for_each(&codec->cvt_setups, i, p) {
+ if (p->nid == nid)
+ return p;
+ }
+ p = snd_array_new(&codec->cvt_setups);
+ if (p)
+ p->nid = nid;
+ return p;
+}
+
+/*
+ * PCM device
+ */
+static void release_pcm(struct kref *kref)
+{
+ struct hda_pcm *pcm = container_of(kref, struct hda_pcm, kref);
+
+ if (pcm->pcm)
+ snd_device_free(pcm->codec->card, pcm->pcm);
+ clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
+ kfree(pcm->name);
+ kfree(pcm);
+}
+
+void snd_hda_codec_pcm_put(struct hda_pcm *pcm)
+{
+ kref_put(&pcm->kref, release_pcm);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put);
+
+struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
+ const char *fmt, ...)
+{
+ struct hda_pcm *pcm;
+ va_list args;
+
+ pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return NULL;
+
+ pcm->codec = codec;
+ kref_init(&pcm->kref);
+ va_start(args, fmt);
+ pcm->name = kvasprintf(GFP_KERNEL, fmt, args);
+ va_end(args);
+ if (!pcm->name) {
+ kfree(pcm);
+ return NULL;
+ }
+
+ list_add_tail(&pcm->list, &codec->pcm_list_head);
+ return pcm;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);
+
+/*
+ * codec destructor
+ */
+static void codec_release_pcms(struct hda_codec *codec)
+{
+ struct hda_pcm *pcm, *n;
+
+ list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) {
+ list_del_init(&pcm->list);
+ if (pcm->pcm)
+ snd_device_disconnect(codec->card, pcm->pcm);
+ snd_hda_codec_pcm_put(pcm);
+ }
+}
+
+void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
+{
+ if (codec->registered) {
+ /* pm_runtime_put() is called in snd_hdac_device_exit() */
+ pm_runtime_get_noresume(hda_codec_dev(codec));
+ pm_runtime_disable(hda_codec_dev(codec));
+ codec->registered = 0;
+ }
+
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ if (!codec->in_freeing)
+ snd_hda_ctls_clear(codec);
+ codec_release_pcms(codec);
+ snd_hda_detach_beep_device(codec);
+ memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
+ snd_hda_jack_tbl_clear(codec);
+ codec->proc_widget_hook = NULL;
+ codec->spec = NULL;
+
+ /* free only driver_pins so that init_pins + user_pins are restored */
+ snd_array_free(&codec->driver_pins);
+ snd_array_free(&codec->cvt_setups);
+ snd_array_free(&codec->spdif_out);
+ snd_array_free(&codec->verbs);
+ codec->follower_dig_outs = NULL;
+ codec->spdif_status_reset = 0;
+ snd_array_free(&codec->mixers);
+ snd_array_free(&codec->nids);
+ remove_conn_list(codec);
+ snd_hdac_regmap_exit(&codec->core);
+ codec->configured = 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup_for_unbind);
+
+static unsigned int hda_set_power_state(struct hda_codec *codec,
+ unsigned int power_state);
+
+/* enable/disable display power per codec */
+static void codec_display_power(struct hda_codec *codec, bool enable)
+{
+ if (codec->display_power_control)
+ snd_hdac_display_power(&codec->bus->core, codec->addr, enable);
+}
+
+/* also called from hda_bind.c */
+void snd_hda_codec_register(struct hda_codec *codec)
+{
+ if (codec->registered)
+ return;
+ if (device_is_registered(hda_codec_dev(codec))) {
+ codec_display_power(codec, true);
+ pm_runtime_enable(hda_codec_dev(codec));
+ /* it was powered up in snd_hda_codec_new(), now all done */
+ snd_hda_power_down(codec);
+ codec->registered = 1;
+ }
+}
+
+static int snd_hda_codec_dev_register(struct snd_device *device)
+{
+ snd_hda_codec_register(device->device_data);
+ return 0;
+}
+
+static int snd_hda_codec_dev_free(struct snd_device *device)
+{
+ struct hda_codec *codec = device->device_data;
+
+ codec->in_freeing = 1;
+ /*
+ * snd_hda_codec_device_new() is used by legacy HDA and ASoC driver.
+ * We can't unregister ASoC device since it will be unregistered in
+ * snd_hdac_ext_bus_device_remove().
+ */
+ if (codec->core.type == HDA_DEV_LEGACY)
+ snd_hdac_device_unregister(&codec->core);
+ codec_display_power(codec, false);
+
+ /*
+ * In the case of ASoC HD-audio bus, the device refcount is released in
+ * snd_hdac_ext_bus_device_remove() explicitly.
+ */
+ if (codec->core.type == HDA_DEV_LEGACY)
+ put_device(hda_codec_dev(codec));
+
+ return 0;
+}
+
+static void snd_hda_codec_dev_release(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ free_init_pincfgs(codec);
+ snd_hdac_device_exit(&codec->core);
+ snd_hda_sysfs_clear(codec);
+ kfree(codec->modelname);
+ kfree(codec->wcaps);
+
+ /*
+ * In the case of ASoC HD-audio, hda_codec is device managed.
+ * It will be freed when the ASoC device is removed.
+ */
+ if (codec->core.type == HDA_DEV_LEGACY)
+ kfree(codec);
+}
+
+#define DEV_NAME_LEN 31
+
+static int snd_hda_codec_device_init(struct hda_bus *bus, struct snd_card *card,
+ unsigned int codec_addr, struct hda_codec **codecp)
+{
+ char name[DEV_NAME_LEN];
+ struct hda_codec *codec;
+ int err;
+
+ dev_dbg(card->dev, "%s: entry\n", __func__);
+
+ if (snd_BUG_ON(!bus))
+ return -EINVAL;
+ if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS))
+ return -EINVAL;
+
+ codec = kzalloc(sizeof(*codec), GFP_KERNEL);
+ if (!codec)
+ return -ENOMEM;
+
+ sprintf(name, "hdaudioC%dD%d", card->number, codec_addr);
+ err = snd_hdac_device_init(&codec->core, &bus->core, name, codec_addr);
+ if (err < 0) {
+ kfree(codec);
+ return err;
+ }
+
+ codec->core.type = HDA_DEV_LEGACY;
+ *codecp = codec;
+
+ return err;
+}
+
+/**
+ * snd_hda_codec_new - create a HDA codec
+ * @bus: the bus to assign
+ * @card: card for this codec
+ * @codec_addr: the codec address
+ * @codecp: the pointer to store the generated codec
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card,
+ unsigned int codec_addr, struct hda_codec **codecp)
+{
+ int ret;
+
+ ret = snd_hda_codec_device_init(bus, card, codec_addr, codecp);
+ if (ret < 0)
+ return ret;
+
+ return snd_hda_codec_device_new(bus, card, codec_addr, *codecp);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_new);
+
+int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card,
+ unsigned int codec_addr, struct hda_codec *codec)
+{
+ char component[31];
+ hda_nid_t fg;
+ int err;
+ static const struct snd_device_ops dev_ops = {
+ .dev_register = snd_hda_codec_dev_register,
+ .dev_free = snd_hda_codec_dev_free,
+ };
+
+ dev_dbg(card->dev, "%s: entry\n", __func__);
+
+ if (snd_BUG_ON(!bus))
+ return -EINVAL;
+ if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS))
+ return -EINVAL;
+
+ codec->core.dev.release = snd_hda_codec_dev_release;
+ codec->core.exec_verb = codec_exec_verb;
+
+ codec->bus = bus;
+ codec->card = card;
+ codec->addr = codec_addr;
+ mutex_init(&codec->spdif_mutex);
+ mutex_init(&codec->control_mutex);
+ snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32);
+ snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32);
+ snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
+ snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
+ snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
+ snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16);
+ snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16);
+ snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8);
+ INIT_LIST_HEAD(&codec->conn_list);
+ INIT_LIST_HEAD(&codec->pcm_list_head);
+
+ INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
+ codec->depop_delay = -1;
+ codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
+
+#ifdef CONFIG_PM
+ codec->power_jiffies = jiffies;
+#endif
+
+ snd_hda_sysfs_init(codec);
+
+ if (codec->bus->modelname) {
+ codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
+ if (!codec->modelname) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+
+ fg = codec->core.afg ? codec->core.afg : codec->core.mfg;
+ err = read_widget_caps(codec, fg);
+ if (err < 0)
+ goto error;
+ err = read_pin_defaults(codec);
+ if (err < 0)
+ goto error;
+
+ /* power-up all before initialization */
+ hda_set_power_state(codec, AC_PWRST_D0);
+ codec->core.dev.power.power_state = PMSG_ON;
+
+ snd_hda_codec_proc_new(codec);
+
+ snd_hda_create_hwdep(codec);
+
+ sprintf(component, "HDA:%08x,%08x,%08x", codec->core.vendor_id,
+ codec->core.subsystem_id, codec->core.revision_id);
+ snd_component_add(card, component);
+
+ err = snd_device_new(card, SNDRV_DEV_CODEC, codec, &dev_ops);
+ if (err < 0)
+ goto error;
+
+ /* PM runtime needs to be enabled later after binding codec */
+ pm_runtime_forbid(&codec->core.dev);
+
+ return 0;
+
+ error:
+ put_device(hda_codec_dev(codec));
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_device_new);
+
+/**
+ * snd_hda_codec_update_widgets - Refresh widget caps and pin defaults
+ * @codec: the HDA codec
+ *
+ * Forcibly refresh the all widget caps and the init pin configurations of
+ * the given codec.
+ */
+int snd_hda_codec_update_widgets(struct hda_codec *codec)
+{
+ hda_nid_t fg;
+ int err;
+
+ err = snd_hdac_refresh_widgets(&codec->core);
+ if (err < 0)
+ return err;
+
+ /* Assume the function group node does not change,
+ * only the widget nodes may change.
+ */
+ kfree(codec->wcaps);
+ fg = codec->core.afg ? codec->core.afg : codec->core.mfg;
+ err = read_widget_caps(codec, fg);
+ if (err < 0)
+ return err;
+
+ snd_array_free(&codec->init_pins);
+ err = read_pin_defaults(codec);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_update_widgets);
+
+/* update the stream-id if changed */
+static void update_pcm_stream_id(struct hda_codec *codec,
+ struct hda_cvt_setup *p, hda_nid_t nid,
+ u32 stream_tag, int channel_id)
+{
+ unsigned int oldval, newval;
+
+ if (p->stream_tag != stream_tag || p->channel_id != channel_id) {
+ oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+ newval = (stream_tag << 4) | channel_id;
+ if (oldval != newval)
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ newval);
+ p->stream_tag = stream_tag;
+ p->channel_id = channel_id;
+ }
+}
+
+/* update the format-id if changed */
+static void update_pcm_format(struct hda_codec *codec, struct hda_cvt_setup *p,
+ hda_nid_t nid, int format)
+{
+ unsigned int oldval;
+
+ if (p->format_id != format) {
+ oldval = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_STREAM_FORMAT, 0);
+ if (oldval != format) {
+ msleep(1);
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_STREAM_FORMAT,
+ format);
+ }
+ p->format_id = format;
+ }
+}
+
+/**
+ * snd_hda_codec_setup_stream - set up the codec for streaming
+ * @codec: the CODEC to set up
+ * @nid: the NID to set up
+ * @stream_tag: stream tag to pass, it's between 0x1 and 0xf.
+ * @channel_id: channel id to pass, zero based.
+ * @format: stream format.
+ */
+void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
+ u32 stream_tag,
+ int channel_id, int format)
+{
+ struct hda_codec *c;
+ struct hda_cvt_setup *p;
+ int type;
+ int i;
+
+ if (!nid)
+ return;
+
+ codec_dbg(codec,
+ "hda_codec_setup_stream: NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
+ nid, stream_tag, channel_id, format);
+ p = get_hda_cvt_setup(codec, nid);
+ if (!p)
+ return;
+
+ if (codec->patch_ops.stream_pm)
+ codec->patch_ops.stream_pm(codec, nid, true);
+ if (codec->pcm_format_first)
+ update_pcm_format(codec, p, nid, format);
+ update_pcm_stream_id(codec, p, nid, stream_tag, channel_id);
+ if (!codec->pcm_format_first)
+ update_pcm_format(codec, p, nid, format);
+
+ p->active = 1;
+ p->dirty = 0;
+
+ /* make other inactive cvts with the same stream-tag dirty */
+ type = get_wcaps_type(get_wcaps(codec, nid));
+ list_for_each_codec(c, codec->bus) {
+ snd_array_for_each(&c->cvt_setups, i, p) {
+ if (!p->active && p->stream_tag == stream_tag &&
+ get_wcaps_type(get_wcaps(c, p->nid)) == type)
+ p->dirty = 1;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_setup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q);
+
+/**
+ * __snd_hda_codec_cleanup_stream - clean up the codec for closing
+ * @codec: the CODEC to clean up
+ * @nid: the NID to clean up
+ * @do_now: really clean up the stream instead of clearing the active flag
+ */
+void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid,
+ int do_now)
+{
+ struct hda_cvt_setup *p;
+
+ if (!nid)
+ return;
+
+ if (codec->no_sticky_stream)
+ do_now = 1;
+
+ codec_dbg(codec, "hda_codec_cleanup_stream: NID=0x%x\n", nid);
+ p = get_hda_cvt_setup(codec, nid);
+ if (p) {
+ /* here we just clear the active flag when do_now isn't set;
+ * actual clean-ups will be done later in
+ * purify_inactive_streams() called from snd_hda_codec_prpapre()
+ */
+ if (do_now)
+ really_cleanup_stream(codec, p);
+ else
+ p->active = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(__snd_hda_codec_cleanup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q)
+{
+ hda_nid_t nid = q->nid;
+ if (q->stream_tag || q->channel_id)
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+ if (q->format_id)
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0
+);
+ memset(q, 0, sizeof(*q));
+ q->nid = nid;
+ if (codec->patch_ops.stream_pm)
+ codec->patch_ops.stream_pm(codec, nid, false);
+}
+
+/* clean up the all conflicting obsolete streams */
+static void purify_inactive_streams(struct hda_codec *codec)
+{
+ struct hda_codec *c;
+ struct hda_cvt_setup *p;
+ int i;
+
+ list_for_each_codec(c, codec->bus) {
+ snd_array_for_each(&c->cvt_setups, i, p) {
+ if (p->dirty)
+ really_cleanup_stream(c, p);
+ }
+ }
+}
+
+#ifdef CONFIG_PM
+/* clean up all streams; called from suspend */
+static void hda_cleanup_all_streams(struct hda_codec *codec)
+{
+ struct hda_cvt_setup *p;
+ int i;
+
+ snd_array_for_each(&codec->cvt_setups, i, p) {
+ if (p->stream_tag)
+ really_cleanup_stream(codec, p);
+ }
+}
+#endif
+
+/*
+ * amp access functions
+ */
+
+/**
+ * query_amp_caps - query AMP capabilities
+ * @codec: the HD-auio codec
+ * @nid: the NID to query
+ * @direction: either #HDA_INPUT or #HDA_OUTPUT
+ *
+ * Query AMP capabilities for the given widget and direction.
+ * Returns the obtained capability bits.
+ *
+ * When cap bits have been already read, this doesn't read again but
+ * returns the cached value.
+ */
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
+{
+ if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
+ nid = codec->core.afg;
+ return snd_hda_param_read(codec, nid,
+ direction == HDA_OUTPUT ?
+ AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+}
+EXPORT_SYMBOL_GPL(query_amp_caps);
+
+/**
+ * snd_hda_check_amp_caps - query AMP capabilities
+ * @codec: the HD-audio codec
+ * @nid: the NID to query
+ * @dir: either #HDA_INPUT or #HDA_OUTPUT
+ * @bits: bit mask to check the result
+ *
+ * Check whether the widget has the given amp capability for the direction.
+ */
+bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int bits)
+{
+ if (!nid)
+ return false;
+ if (get_wcaps(codec, nid) & (1 << (dir + 1)))
+ if (query_amp_caps(codec, nid, dir) & bits)
+ return true;
+ return false;
+}
+EXPORT_SYMBOL_GPL(snd_hda_check_amp_caps);
+
+/**
+ * snd_hda_override_amp_caps - Override the AMP capabilities
+ * @codec: the CODEC to clean up
+ * @nid: the NID to clean up
+ * @dir: either #HDA_INPUT or #HDA_OUTPUT
+ * @caps: the capability bits to set
+ *
+ * Override the cached AMP caps bits value by the given one.
+ * This function is useful if the driver needs to adjust the AMP ranges,
+ * e.g. limit to 0dB, etc.
+ *
+ * Returns zero if successful or a negative error code.
+ */
+int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int caps)
+{
+ unsigned int parm;
+
+ snd_hda_override_wcaps(codec, nid,
+ get_wcaps(codec, nid) | AC_WCAP_AMP_OVRD);
+ parm = dir == HDA_OUTPUT ? AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP;
+ return snd_hdac_override_parm(&codec->core, nid, parm, caps);
+}
+EXPORT_SYMBOL_GPL(snd_hda_override_amp_caps);
+
+static unsigned int encode_amp(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx)
+{
+ unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx);
+
+ /* enable fake mute if no h/w mute but min=mute */
+ if ((query_amp_caps(codec, nid, dir) &
+ (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) == AC_AMPCAP_MIN_MUTE)
+ cmd |= AC_AMP_FAKE_MUTE;
+ return cmd;
+}
+
+/**
+ * snd_hda_codec_amp_update - update the AMP mono value
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @ch: channel to update (0 or 1)
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the AMP values for the given channel, direction and index.
+ */
+int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, int mask, int val)
+{
+ unsigned int cmd = encode_amp(codec, nid, ch, dir, idx);
+
+ return snd_hdac_regmap_update_raw(&codec->core, cmd, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_update);
+
+/**
+ * snd_hda_codec_amp_stereo - update the AMP stereo values
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the AMP values like snd_hda_codec_amp_update(), but for a
+ * stereo widget with the same mask and value.
+ */
+int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int direction, int idx, int mask, int val)
+{
+ int ch, ret = 0;
+
+ if (snd_BUG_ON(mask & ~0xff))
+ mask &= 0xff;
+ for (ch = 0; ch < 2; ch++)
+ ret |= snd_hda_codec_amp_update(codec, nid, ch, direction,
+ idx, mask, val);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_stereo);
+
+/**
+ * snd_hda_codec_amp_init - initialize the AMP value
+ * @codec: the HDA codec
+ * @nid: NID to read the AMP value
+ * @ch: channel (left=0 or right=1)
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Works like snd_hda_codec_amp_update() but it writes the value only at
+ * the first access. If the amp was already initialized / updated beforehand,
+ * this does nothing.
+ */
+int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
+ int dir, int idx, int mask, int val)
+{
+ unsigned int cmd = encode_amp(codec, nid, ch, dir, idx);
+
+ if (!codec->core.regmap)
+ return -EINVAL;
+ return snd_hdac_regmap_update_raw_once(&codec->core, cmd, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init);
+
+/**
+ * snd_hda_codec_amp_init_stereo - initialize the stereo AMP value
+ * @codec: the HDA codec
+ * @nid: NID to read the AMP value
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Call snd_hda_codec_amp_init() for both stereo channels.
+ */
+int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx, int mask, int val)
+{
+ int ch, ret = 0;
+
+ if (snd_BUG_ON(mask & ~0xff))
+ mask &= 0xff;
+ for (ch = 0; ch < 2; ch++)
+ ret |= snd_hda_codec_amp_init(codec, nid, ch, dir,
+ idx, mask, val);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init_stereo);
+
+static u32 get_amp_max_value(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int ofs)
+{
+ u32 caps = query_amp_caps(codec, nid, dir);
+ /* get num steps */
+ caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ if (ofs < caps)
+ caps -= ofs;
+ return caps;
+}
+
+/**
+ * snd_hda_mixer_amp_volume_info - Info callback for a standard AMP mixer
+ * @kcontrol: referred ctl element
+ * @uinfo: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 nid = get_amp_nid(kcontrol);
+ u8 chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = get_amp_max_value(codec, nid, dir, ofs);
+ if (!uinfo->value.integer.max) {
+ codec_warn(codec,
+ "num_steps = 0 for NID=0x%x (ctl = %s)\n",
+ nid, kcontrol->id.name);
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_info);
+
+
+static inline unsigned int
+read_amp_value(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, unsigned int ofs)
+{
+ unsigned int val;
+ val = snd_hda_codec_amp_read(codec, nid, ch, dir, idx);
+ val &= HDA_AMP_VOLMASK;
+ if (val >= ofs)
+ val -= ofs;
+ else
+ val = 0;
+ return val;
+}
+
+static inline int
+update_amp_value(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, unsigned int ofs,
+ unsigned int val)
+{
+ unsigned int maxval;
+
+ if (val > 0)
+ val += ofs;
+ /* ofs = 0: raw max value */
+ maxval = get_amp_max_value(codec, nid, dir, 0);
+ if (val > maxval)
+ val = maxval;
+ return snd_hda_codec_amp_update(codec, nid, ch, dir, idx,
+ HDA_AMP_VOLMASK, val);
+}
+
+/**
+ * snd_hda_mixer_amp_volume_get - Get callback for a standard AMP mixer volume
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+ *valp++ = read_amp_value(codec, nid, 0, dir, idx, ofs);
+ if (chs & 2)
+ *valp = read_amp_value(codec, nid, 1, dir, idx, ofs);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_get);
+
+/**
+ * snd_hda_mixer_amp_volume_put - Put callback for a standard AMP mixer volume
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+
+ if (chs & 1) {
+ change = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp);
+ valp++;
+ }
+ if (chs & 2)
+ change |= update_amp_value(codec, nid, 1, dir, idx, ofs, *valp);
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_put);
+
+/* inquiry the amp caps and convert to TLV */
+static void get_ctl_amp_tlv(struct snd_kcontrol *kcontrol, unsigned int *tlv)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+ bool min_mute = get_amp_min_mute(kcontrol);
+ u32 caps, val1, val2;
+
+ caps = query_amp_caps(codec, nid, dir);
+ val2 = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ val2 = (val2 + 1) * 25;
+ val1 = -((caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT);
+ val1 += ofs;
+ val1 = ((int)val1) * ((int)val2);
+ if (min_mute || (caps & AC_AMPCAP_MIN_MUTE))
+ val2 |= TLV_DB_SCALE_MUTE;
+ tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE;
+ tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int);
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = val1;
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = val2;
+}
+
+/**
+ * snd_hda_mixer_amp_tlv - TLV callback for a standard AMP mixer volume
+ * @kcontrol: ctl element
+ * @op_flag: operation flag
+ * @size: byte size of input TLV
+ * @_tlv: TLV data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *_tlv)
+{
+ unsigned int tlv[4];
+
+ if (size < 4 * sizeof(unsigned int))
+ return -ENOMEM;
+ get_ctl_amp_tlv(kcontrol, tlv);
+ if (copy_to_user(_tlv, tlv, sizeof(tlv)))
+ return -EFAULT;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_tlv);
+
+/**
+ * snd_hda_set_vmaster_tlv - Set TLV for a virtual master control
+ * @codec: HD-audio codec
+ * @nid: NID of a reference widget
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @tlv: TLV data to be stored, at least 4 elements
+ *
+ * Set (static) TLV data for a virtual master volume using the AMP caps
+ * obtained from the reference NID.
+ * The volume range is recalculated as if the max volume is 0dB.
+ */
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int *tlv)
+{
+ u32 caps;
+ int nums, step;
+
+ caps = query_amp_caps(codec, nid, dir);
+ nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ step = (step + 1) * 25;
+ tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE;
+ tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int);
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = -nums * step;
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = step;
+}
+EXPORT_SYMBOL_GPL(snd_hda_set_vmaster_tlv);
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *
+find_mixer_ctl(struct hda_codec *codec, const char *name, int dev, int idx)
+{
+ struct snd_ctl_elem_id id;
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ id.device = dev;
+ id.index = idx;
+ if (snd_BUG_ON(strlen(name) >= sizeof(id.name)))
+ return NULL;
+ strcpy(id.name, name);
+ return snd_ctl_find_id(codec->card, &id);
+}
+
+/**
+ * snd_hda_find_mixer_ctl - Find a mixer control element with the given name
+ * @codec: HD-audio codec
+ * @name: ctl id name string
+ *
+ * Get the control element with the given id string and IFACE_MIXER.
+ */
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+ const char *name)
+{
+ return find_mixer_ctl(codec, name, 0, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hda_find_mixer_ctl);
+
+static int find_empty_mixer_ctl_idx(struct hda_codec *codec, const char *name,
+ int start_idx)
+{
+ int i, idx;
+ /* 16 ctlrs should be large enough */
+ for (i = 0, idx = start_idx; i < 16; i++, idx++) {
+ if (!find_mixer_ctl(codec, name, 0, idx))
+ return idx;
+ }
+ return -EBUSY;
+}
+
+/**
+ * snd_hda_ctl_add - Add a control element and assign to the codec
+ * @codec: HD-audio codec
+ * @nid: corresponding NID (optional)
+ * @kctl: the control element to assign
+ *
+ * Add the given control element to an array inside the codec instance.
+ * All control elements belonging to a codec are supposed to be added
+ * by this function so that a proper clean-up works at the free or
+ * reconfiguration time.
+ *
+ * If non-zero @nid is passed, the NID is assigned to the control element.
+ * The assignment is shown in the codec proc file.
+ *
+ * snd_hda_ctl_add() checks the control subdev id field whether
+ * #HDA_SUBDEV_NID_FLAG bit is set. If set (and @nid is zero), the lower
+ * bits value is taken as the NID to assign. The #HDA_NID_ITEM_AMP bit
+ * specifies if kctl->private_value is a HDA amplifier value.
+ */
+int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
+ struct snd_kcontrol *kctl)
+{
+ int err;
+ unsigned short flags = 0;
+ struct hda_nid_item *item;
+
+ if (kctl->id.subdevice & HDA_SUBDEV_AMP_FLAG) {
+ flags |= HDA_NID_ITEM_AMP;
+ if (nid == 0)
+ nid = get_amp_nid_(kctl->private_value);
+ }
+ if ((kctl->id.subdevice & HDA_SUBDEV_NID_FLAG) != 0 && nid == 0)
+ nid = kctl->id.subdevice & 0xffff;
+ if (kctl->id.subdevice & (HDA_SUBDEV_NID_FLAG|HDA_SUBDEV_AMP_FLAG))
+ kctl->id.subdevice = 0;
+ err = snd_ctl_add(codec->card, kctl);
+ if (err < 0)
+ return err;
+ item = snd_array_new(&codec->mixers);
+ if (!item)
+ return -ENOMEM;
+ item->kctl = kctl;
+ item->nid = nid;
+ item->flags = flags;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_ctl_add);
+
+/**
+ * snd_hda_add_nid - Assign a NID to a control element
+ * @codec: HD-audio codec
+ * @nid: corresponding NID (optional)
+ * @kctl: the control element to assign
+ * @index: index to kctl
+ *
+ * Add the given control element to an array inside the codec instance.
+ * This function is used when #snd_hda_ctl_add cannot be used for 1:1
+ * NID:KCTL mapping - for example "Capture Source" selector.
+ */
+int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
+ unsigned int index, hda_nid_t nid)
+{
+ struct hda_nid_item *item;
+
+ if (nid > 0) {
+ item = snd_array_new(&codec->nids);
+ if (!item)
+ return -ENOMEM;
+ item->kctl = kctl;
+ item->index = index;
+ item->nid = nid;
+ return 0;
+ }
+ codec_err(codec, "no NID for mapping control %s:%d:%d\n",
+ kctl->id.name, kctl->id.index, index);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_nid);
+
+/**
+ * snd_hda_ctls_clear - Clear all controls assigned to the given codec
+ * @codec: HD-audio codec
+ */
+void snd_hda_ctls_clear(struct hda_codec *codec)
+{
+ int i;
+ struct hda_nid_item *items = codec->mixers.list;
+
+ down_write(&codec->card->controls_rwsem);
+ for (i = 0; i < codec->mixers.used; i++)
+ snd_ctl_remove(codec->card, items[i].kctl);
+ up_write(&codec->card->controls_rwsem);
+ snd_array_free(&codec->mixers);
+ snd_array_free(&codec->nids);
+}
+
+/**
+ * snd_hda_lock_devices - pseudo device locking
+ * @bus: the BUS
+ *
+ * toggle card->shutdown to allow/disallow the device access (as a hack)
+ */
+int snd_hda_lock_devices(struct hda_bus *bus)
+{
+ struct snd_card *card = bus->card;
+ struct hda_codec *codec;
+
+ spin_lock(&card->files_lock);
+ if (card->shutdown)
+ goto err_unlock;
+ card->shutdown = 1;
+ if (!list_empty(&card->ctl_files))
+ goto err_clear;
+
+ list_for_each_codec(codec, bus) {
+ struct hda_pcm *cpcm;
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ if (!cpcm->pcm)
+ continue;
+ if (cpcm->pcm->streams[0].substream_opened ||
+ cpcm->pcm->streams[1].substream_opened)
+ goto err_clear;
+ }
+ }
+ spin_unlock(&card->files_lock);
+ return 0;
+
+ err_clear:
+ card->shutdown = 0;
+ err_unlock:
+ spin_unlock(&card->files_lock);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_lock_devices);
+
+/**
+ * snd_hda_unlock_devices - pseudo device unlocking
+ * @bus: the BUS
+ */
+void snd_hda_unlock_devices(struct hda_bus *bus)
+{
+ struct snd_card *card = bus->card;
+
+ spin_lock(&card->files_lock);
+ card->shutdown = 0;
+ spin_unlock(&card->files_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hda_unlock_devices);
+
+/**
+ * snd_hda_codec_reset - Clear all objects assigned to the codec
+ * @codec: HD-audio codec
+ *
+ * This frees the all PCM and control elements assigned to the codec, and
+ * clears the caches and restores the pin default configurations.
+ *
+ * When a device is being used, it returns -EBSY. If successfully freed,
+ * returns zero.
+ */
+int snd_hda_codec_reset(struct hda_codec *codec)
+{
+ struct hda_bus *bus = codec->bus;
+
+ if (snd_hda_lock_devices(bus) < 0)
+ return -EBUSY;
+
+ /* OK, let it free */
+ device_release_driver(hda_codec_dev(codec));
+
+ /* allow device access again */
+ snd_hda_unlock_devices(bus);
+ return 0;
+}
+
+typedef int (*map_follower_func_t)(struct hda_codec *, void *, struct snd_kcontrol *);
+
+/* apply the function to all matching follower ctls in the mixer list */
+static int map_followers(struct hda_codec *codec, const char * const *followers,
+ const char *suffix, map_follower_func_t func, void *data)
+{
+ struct hda_nid_item *items;
+ const char * const *s;
+ int i, err;
+
+ items = codec->mixers.list;
+ for (i = 0; i < codec->mixers.used; i++) {
+ struct snd_kcontrol *sctl = items[i].kctl;
+ if (!sctl || sctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER)
+ continue;
+ for (s = followers; *s; s++) {
+ char tmpname[sizeof(sctl->id.name)];
+ const char *name = *s;
+ if (suffix) {
+ snprintf(tmpname, sizeof(tmpname), "%s %s",
+ name, suffix);
+ name = tmpname;
+ }
+ if (!strcmp(sctl->id.name, name)) {
+ err = func(codec, data, sctl);
+ if (err)
+ return err;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static int check_follower_present(struct hda_codec *codec,
+ void *data, struct snd_kcontrol *sctl)
+{
+ return 1;
+}
+
+/* call kctl->put with the given value(s) */
+static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
+{
+ struct snd_ctl_elem_value *ucontrol;
+ ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL);
+ if (!ucontrol)
+ return -ENOMEM;
+ ucontrol->value.integer.value[0] = val;
+ ucontrol->value.integer.value[1] = val;
+ kctl->put(kctl, ucontrol);
+ kfree(ucontrol);
+ return 0;
+}
+
+struct follower_init_arg {
+ struct hda_codec *codec;
+ int step;
+};
+
+/* initialize the follower volume with 0dB via snd_ctl_apply_vmaster_followers() */
+static int init_follower_0dB(struct snd_kcontrol *follower,
+ struct snd_kcontrol *kctl,
+ void *_arg)
+{
+ struct follower_init_arg *arg = _arg;
+ int _tlv[4];
+ const int *tlv = NULL;
+ int step;
+ int val;
+
+ if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+ if (kctl->tlv.c != snd_hda_mixer_amp_tlv) {
+ codec_err(arg->codec,
+ "Unexpected TLV callback for follower %s:%d\n",
+ kctl->id.name, kctl->id.index);
+ return 0; /* ignore */
+ }
+ get_ctl_amp_tlv(kctl, _tlv);
+ tlv = _tlv;
+ } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)
+ tlv = kctl->tlv.p;
+
+ if (!tlv || tlv[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE)
+ return 0;
+
+ step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP];
+ step &= ~TLV_DB_SCALE_MUTE;
+ if (!step)
+ return 0;
+ if (arg->step && arg->step != step) {
+ codec_err(arg->codec,
+ "Mismatching dB step for vmaster follower (%d!=%d)\n",
+ arg->step, step);
+ return 0;
+ }
+
+ arg->step = step;
+ val = -tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] / step;
+ if (val > 0) {
+ put_kctl_with_value(follower, val);
+ return val;
+ }
+
+ return 0;
+}
+
+/* unmute the follower via snd_ctl_apply_vmaster_followers() */
+static int init_follower_unmute(struct snd_kcontrol *follower,
+ struct snd_kcontrol *kctl,
+ void *_arg)
+{
+ return put_kctl_with_value(follower, 1);
+}
+
+static int add_follower(struct hda_codec *codec,
+ void *data, struct snd_kcontrol *follower)
+{
+ return snd_ctl_add_follower(data, follower);
+}
+
+/**
+ * __snd_hda_add_vmaster - create a virtual master control and add followers
+ * @codec: HD-audio codec
+ * @name: vmaster control name
+ * @tlv: TLV data (optional)
+ * @followers: follower control names (optional)
+ * @suffix: suffix string to each follower name (optional)
+ * @init_follower_vol: initialize followers to unmute/0dB
+ * @ctl_ret: store the vmaster kcontrol in return
+ *
+ * Create a virtual master control with the given name. The TLV data
+ * must be either NULL or a valid data.
+ *
+ * @followers is a NULL-terminated array of strings, each of which is a
+ * follower control name. All controls with these names are assigned to
+ * the new virtual master control.
+ *
+ * This function returns zero if successful or a negative error code.
+ */
+int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+ unsigned int *tlv, const char * const *followers,
+ const char *suffix, bool init_follower_vol,
+ struct snd_kcontrol **ctl_ret)
+{
+ struct snd_kcontrol *kctl;
+ int err;
+
+ if (ctl_ret)
+ *ctl_ret = NULL;
+
+ err = map_followers(codec, followers, suffix, check_follower_present, NULL);
+ if (err != 1) {
+ codec_dbg(codec, "No follower found for %s\n", name);
+ return 0;
+ }
+ kctl = snd_ctl_make_virtual_master(name, tlv);
+ if (!kctl)
+ return -ENOMEM;
+ err = snd_hda_ctl_add(codec, 0, kctl);
+ if (err < 0)
+ return err;
+
+ err = map_followers(codec, followers, suffix, add_follower, kctl);
+ if (err < 0)
+ return err;
+
+ /* init with master mute & zero volume */
+ put_kctl_with_value(kctl, 0);
+ if (init_follower_vol) {
+ struct follower_init_arg arg = {
+ .codec = codec,
+ .step = 0,
+ };
+ snd_ctl_apply_vmaster_followers(kctl,
+ tlv ? init_follower_0dB : init_follower_unmute,
+ &arg);
+ }
+
+ if (ctl_ret)
+ *ctl_ret = kctl;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__snd_hda_add_vmaster);
+
+/*
+ * mute-LED control using vmaster
+ */
+static int vmaster_mute_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "On", "Off", "Follow Master"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int vmaster_mute_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_vmaster_mute_hook *hook = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = hook->mute_mode;
+ return 0;
+}
+
+static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_vmaster_mute_hook *hook = snd_kcontrol_chip(kcontrol);
+ unsigned int old_mode = hook->mute_mode;
+
+ hook->mute_mode = ucontrol->value.enumerated.item[0];
+ if (hook->mute_mode > HDA_VMUTE_FOLLOW_MASTER)
+ hook->mute_mode = HDA_VMUTE_FOLLOW_MASTER;
+ if (old_mode == hook->mute_mode)
+ return 0;
+ snd_hda_sync_vmaster_hook(hook);
+ return 1;
+}
+
+static const struct snd_kcontrol_new vmaster_mute_mode = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mute-LED Mode",
+ .info = vmaster_mute_mode_info,
+ .get = vmaster_mute_mode_get,
+ .put = vmaster_mute_mode_put,
+};
+
+/* meta hook to call each driver's vmaster hook */
+static void vmaster_hook(void *private_data, int enabled)
+{
+ struct hda_vmaster_mute_hook *hook = private_data;
+
+ if (hook->mute_mode != HDA_VMUTE_FOLLOW_MASTER)
+ enabled = hook->mute_mode;
+ hook->hook(hook->codec, enabled);
+}
+
+/**
+ * snd_hda_add_vmaster_hook - Add a vmaster hook for mute-LED
+ * @codec: the HDA codec
+ * @hook: the vmaster hook object
+ * @expose_enum_ctl: flag to create an enum ctl
+ *
+ * Add a mute-LED hook with the given vmaster switch kctl.
+ * When @expose_enum_ctl is set, "Mute-LED Mode" control is automatically
+ * created and associated with the given hook.
+ */
+int snd_hda_add_vmaster_hook(struct hda_codec *codec,
+ struct hda_vmaster_mute_hook *hook,
+ bool expose_enum_ctl)
+{
+ struct snd_kcontrol *kctl;
+
+ if (!hook->hook || !hook->sw_kctl)
+ return 0;
+ hook->codec = codec;
+ hook->mute_mode = HDA_VMUTE_FOLLOW_MASTER;
+ snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook);
+ if (!expose_enum_ctl)
+ return 0;
+ kctl = snd_ctl_new1(&vmaster_mute_mode, hook);
+ if (!kctl)
+ return -ENOMEM;
+ return snd_hda_ctl_add(codec, 0, kctl);
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook);
+
+/**
+ * snd_hda_sync_vmaster_hook - Sync vmaster hook
+ * @hook: the vmaster hook
+ *
+ * Call the hook with the current value for synchronization.
+ * Should be called in init callback.
+ */
+void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook)
+{
+ if (!hook->hook || !hook->codec)
+ return;
+ /* don't call vmaster hook in the destructor since it might have
+ * been already destroyed
+ */
+ if (hook->codec->bus->shutdown)
+ return;
+ snd_ctl_sync_vmaster_hook(hook->sw_kctl);
+}
+EXPORT_SYMBOL_GPL(snd_hda_sync_vmaster_hook);
+
+
+/**
+ * snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch
+ * @kcontrol: referred ctl element
+ * @uinfo: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_info);
+
+/**
+ * snd_hda_mixer_amp_switch_get - Get callback for a standard AMP mixer switch
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+ *valp++ = (snd_hda_codec_amp_read(codec, nid, 0, dir, idx) &
+ HDA_AMP_MUTE) ? 0 : 1;
+ if (chs & 2)
+ *valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) &
+ HDA_AMP_MUTE) ? 0 : 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get);
+
+/**
+ * snd_hda_mixer_amp_switch_put - Put callback for a standard AMP mixer switch
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+
+ if (chs & 1) {
+ change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+ HDA_AMP_MUTE,
+ *valp ? 0 : HDA_AMP_MUTE);
+ valp++;
+ }
+ if (chs & 2)
+ change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+ HDA_AMP_MUTE,
+ *valp ? 0 : HDA_AMP_MUTE);
+ hda_call_check_power_status(codec, nid);
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put);
+
+/*
+ * SPDIF out controls
+ */
+
+static int snd_hda_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_hda_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;
+ return 0;
+}
+
+static int snd_hda_spdif_pmask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PRO_EMPHASIS_5015;
+ return 0;
+}
+
+static int snd_hda_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ ucontrol->value.iec958.status[0] = spdif->status & 0xff;
+ ucontrol->value.iec958.status[1] = (spdif->status >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (spdif->status >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (spdif->status >> 24) & 0xff;
+ mutex_unlock(&codec->spdif_mutex);
+
+ return 0;
+}
+
+/* convert from SPDIF status bits to HDA SPDIF bits
+ * bit 0 (DigEn) is always set zero (to be filled later)
+ */
+static unsigned short convert_from_spdif_status(unsigned int sbits)
+{
+ unsigned short val = 0;
+
+ if (sbits & IEC958_AES0_PROFESSIONAL)
+ val |= AC_DIG1_PROFESSIONAL;
+ if (sbits & IEC958_AES0_NONAUDIO)
+ val |= AC_DIG1_NONAUDIO;
+ if (sbits & IEC958_AES0_PROFESSIONAL) {
+ if ((sbits & IEC958_AES0_PRO_EMPHASIS) ==
+ IEC958_AES0_PRO_EMPHASIS_5015)
+ val |= AC_DIG1_EMPHASIS;
+ } else {
+ if ((sbits & IEC958_AES0_CON_EMPHASIS) ==
+ IEC958_AES0_CON_EMPHASIS_5015)
+ val |= AC_DIG1_EMPHASIS;
+ if (!(sbits & IEC958_AES0_CON_NOT_COPYRIGHT))
+ val |= AC_DIG1_COPYRIGHT;
+ if (sbits & (IEC958_AES1_CON_ORIGINAL << 8))
+ val |= AC_DIG1_LEVEL;
+ val |= sbits & (IEC958_AES1_CON_CATEGORY << 8);
+ }
+ return val;
+}
+
+/* convert to SPDIF status bits from HDA SPDIF bits
+ */
+static unsigned int convert_to_spdif_status(unsigned short val)
+{
+ unsigned int sbits = 0;
+
+ if (val & AC_DIG1_NONAUDIO)
+ sbits |= IEC958_AES0_NONAUDIO;
+ if (val & AC_DIG1_PROFESSIONAL)
+ sbits |= IEC958_AES0_PROFESSIONAL;
+ if (sbits & IEC958_AES0_PROFESSIONAL) {
+ if (val & AC_DIG1_EMPHASIS)
+ sbits |= IEC958_AES0_PRO_EMPHASIS_5015;
+ } else {
+ if (val & AC_DIG1_EMPHASIS)
+ sbits |= IEC958_AES0_CON_EMPHASIS_5015;
+ if (!(val & AC_DIG1_COPYRIGHT))
+ sbits |= IEC958_AES0_CON_NOT_COPYRIGHT;
+ if (val & AC_DIG1_LEVEL)
+ sbits |= (IEC958_AES1_CON_ORIGINAL << 8);
+ sbits |= val & (0x7f << 8);
+ }
+ return sbits;
+}
+
+/* set digital convert verbs both for the given NID and its followers */
+static void set_dig_out(struct hda_codec *codec, hda_nid_t nid,
+ int mask, int val)
+{
+ const hda_nid_t *d;
+
+ snd_hdac_regmap_update(&codec->core, nid, AC_VERB_SET_DIGI_CONVERT_1,
+ mask, val);
+ d = codec->follower_dig_outs;
+ if (!d)
+ return;
+ for (; *d; d++)
+ snd_hdac_regmap_update(&codec->core, *d,
+ AC_VERB_SET_DIGI_CONVERT_1, mask, val);
+}
+
+static inline void set_dig_out_convert(struct hda_codec *codec, hda_nid_t nid,
+ int dig1, int dig2)
+{
+ unsigned int mask = 0;
+ unsigned int val = 0;
+
+ if (dig1 != -1) {
+ mask |= 0xff;
+ val = dig1;
+ }
+ if (dig2 != -1) {
+ mask |= 0xff00;
+ val |= dig2 << 8;
+ }
+ set_dig_out(codec, nid, mask, val);
+}
+
+static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+ hda_nid_t nid;
+ unsigned short val;
+ int change;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ nid = spdif->nid;
+ spdif->status = ucontrol->value.iec958.status[0] |
+ ((unsigned int)ucontrol->value.iec958.status[1] << 8) |
+ ((unsigned int)ucontrol->value.iec958.status[2] << 16) |
+ ((unsigned int)ucontrol->value.iec958.status[3] << 24);
+ val = convert_from_spdif_status(spdif->status);
+ val |= spdif->ctls & 1;
+ change = spdif->ctls != val;
+ spdif->ctls = val;
+ if (change && nid != (u16)-1)
+ set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff);
+ mutex_unlock(&codec->spdif_mutex);
+ return change;
+}
+
+#define snd_hda_spdif_out_switch_info snd_ctl_boolean_mono_info
+
+static int snd_hda_spdif_out_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ ucontrol->value.integer.value[0] = spdif->ctls & AC_DIG1_ENABLE;
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+
+static inline void set_spdif_ctls(struct hda_codec *codec, hda_nid_t nid,
+ int dig1, int dig2)
+{
+ set_dig_out_convert(codec, nid, dig1, dig2);
+ /* unmute amp switch (if any) */
+ if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) &&
+ (dig1 & AC_DIG1_ENABLE))
+ snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+ HDA_AMP_MUTE, 0);
+}
+
+static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+ hda_nid_t nid;
+ unsigned short val;
+ int change;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ nid = spdif->nid;
+ val = spdif->ctls & ~AC_DIG1_ENABLE;
+ if (ucontrol->value.integer.value[0])
+ val |= AC_DIG1_ENABLE;
+ change = spdif->ctls != val;
+ spdif->ctls = val;
+ if (change && nid != (u16)-1)
+ set_spdif_ctls(codec, nid, val & 0xff, -1);
+ mutex_unlock(&codec->spdif_mutex);
+ return change;
+}
+
+static const struct snd_kcontrol_new dig_mixes[] = {
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_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_hda_spdif_mask_info,
+ .get = snd_hda_spdif_pmask_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_default_get,
+ .put = snd_hda_spdif_default_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+ .info = snd_hda_spdif_out_switch_info,
+ .get = snd_hda_spdif_out_switch_get,
+ .put = snd_hda_spdif_out_switch_put,
+ },
+ { } /* end */
+};
+
+/**
+ * snd_hda_create_dig_out_ctls - create Output SPDIF-related controls
+ * @codec: the HDA codec
+ * @associated_nid: NID that new ctls associated with
+ * @cvt_nid: converter NID
+ * @type: HDA_PCM_TYPE_*
+ * Creates controls related with the digital output.
+ * Called from each patch supporting the digital out.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_dig_out_ctls(struct hda_codec *codec,
+ hda_nid_t associated_nid,
+ hda_nid_t cvt_nid,
+ int type)
+{
+ int err;
+ struct snd_kcontrol *kctl;
+ const struct snd_kcontrol_new *dig_mix;
+ int idx = 0;
+ int val = 0;
+ const int spdif_index = 16;
+ struct hda_spdif_out *spdif;
+ struct hda_bus *bus = codec->bus;
+
+ if (bus->primary_dig_out_type == HDA_PCM_TYPE_HDMI &&
+ type == HDA_PCM_TYPE_SPDIF) {
+ idx = spdif_index;
+ } else if (bus->primary_dig_out_type == HDA_PCM_TYPE_SPDIF &&
+ type == HDA_PCM_TYPE_HDMI) {
+ /* suppose a single SPDIF device */
+ for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
+ kctl = find_mixer_ctl(codec, dig_mix->name, 0, 0);
+ if (!kctl)
+ break;
+ kctl->id.index = spdif_index;
+ }
+ bus->primary_dig_out_type = HDA_PCM_TYPE_HDMI;
+ }
+ if (!bus->primary_dig_out_type)
+ bus->primary_dig_out_type = type;
+
+ idx = find_empty_mixer_ctl_idx(codec, "IEC958 Playback Switch", idx);
+ if (idx < 0) {
+ codec_err(codec, "too many IEC958 outputs\n");
+ return -EBUSY;
+ }
+ spdif = snd_array_new(&codec->spdif_out);
+ if (!spdif)
+ return -ENOMEM;
+ for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
+ kctl = snd_ctl_new1(dig_mix, codec);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->id.index = idx;
+ kctl->private_value = codec->spdif_out.used - 1;
+ err = snd_hda_ctl_add(codec, associated_nid, kctl);
+ if (err < 0)
+ return err;
+ }
+ spdif->nid = cvt_nid;
+ snd_hdac_regmap_read(&codec->core, cvt_nid,
+ AC_VERB_GET_DIGI_CONVERT_1, &val);
+ spdif->ctls = val;
+ spdif->status = convert_to_spdif_status(spdif->ctls);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_create_dig_out_ctls);
+
+/**
+ * snd_hda_spdif_out_of_nid - get the hda_spdif_out entry from the given NID
+ * @codec: the HDA codec
+ * @nid: widget NID
+ *
+ * call within spdif_mutex lock
+ */
+struct hda_spdif_out *snd_hda_spdif_out_of_nid(struct hda_codec *codec,
+ hda_nid_t nid)
+{
+ struct hda_spdif_out *spdif;
+ int i;
+
+ snd_array_for_each(&codec->spdif_out, i, spdif) {
+ if (spdif->nid == nid)
+ return spdif;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_spdif_out_of_nid);
+
+/**
+ * snd_hda_spdif_ctls_unassign - Unassign the given SPDIF ctl
+ * @codec: the HDA codec
+ * @idx: the SPDIF ctl index
+ *
+ * Unassign the widget from the given SPDIF control.
+ */
+void snd_hda_spdif_ctls_unassign(struct hda_codec *codec, int idx)
+{
+ struct hda_spdif_out *spdif;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ spdif->nid = (u16)-1;
+ mutex_unlock(&codec->spdif_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_unassign);
+
+/**
+ * snd_hda_spdif_ctls_assign - Assign the SPDIF controls to the given NID
+ * @codec: the HDA codec
+ * @idx: the SPDIF ctl idx
+ * @nid: widget NID
+ *
+ * Assign the widget to the SPDIF control with the given index.
+ */
+void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid)
+{
+ struct hda_spdif_out *spdif;
+ unsigned short val;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ if (spdif->nid != nid) {
+ spdif->nid = nid;
+ val = spdif->ctls;
+ set_spdif_ctls(codec, nid, val & 0xff, (val >> 8) & 0xff);
+ }
+ mutex_unlock(&codec->spdif_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign);
+
+/*
+ * SPDIF sharing with analog output
+ */
+static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = mout->share_spdif;
+ return 0;
+}
+
+static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+ mout->share_spdif = !!ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static const struct snd_kcontrol_new spdif_share_sw = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "IEC958 Default PCM Playback Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = spdif_share_sw_get,
+ .put = spdif_share_sw_put,
+};
+
+/**
+ * snd_hda_create_spdif_share_sw - create Default PCM switch
+ * @codec: the HDA codec
+ * @mout: multi-out instance
+ */
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ struct snd_kcontrol *kctl;
+
+ if (!mout->dig_out_nid)
+ return 0;
+
+ kctl = snd_ctl_new1(&spdif_share_sw, mout);
+ if (!kctl)
+ return -ENOMEM;
+ /* ATTENTION: here mout is passed as private_data, instead of codec */
+ return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl);
+}
+EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw);
+
+/*
+ * SPDIF input
+ */
+
+#define snd_hda_spdif_in_switch_info snd_hda_spdif_out_switch_info
+
+static int snd_hda_spdif_in_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = codec->spdif_in_enable;
+ return 0;
+}
+
+static int snd_hda_spdif_in_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val = !!ucontrol->value.integer.value[0];
+ int change;
+
+ mutex_lock(&codec->spdif_mutex);
+ change = codec->spdif_in_enable != val;
+ if (change) {
+ codec->spdif_in_enable = val;
+ snd_hdac_regmap_write(&codec->core, nid,
+ AC_VERB_SET_DIGI_CONVERT_1, val);
+ }
+ mutex_unlock(&codec->spdif_mutex);
+ return change;
+}
+
+static int snd_hda_spdif_in_status_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val;
+ unsigned int sbits;
+
+ snd_hdac_regmap_read(&codec->core, nid,
+ AC_VERB_GET_DIGI_CONVERT_1, &val);
+ sbits = convert_to_spdif_status(val);
+ ucontrol->value.iec958.status[0] = sbits;
+ ucontrol->value.iec958.status[1] = sbits >> 8;
+ ucontrol->value.iec958.status[2] = sbits >> 16;
+ ucontrol->value.iec958.status[3] = sbits >> 24;
+ return 0;
+}
+
+static const struct snd_kcontrol_new dig_in_ctls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH),
+ .info = snd_hda_spdif_in_switch_info,
+ .get = snd_hda_spdif_in_switch_get,
+ .put = snd_hda_spdif_in_switch_put,
+ },
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_in_status_get,
+ },
+ { } /* end */
+};
+
+/**
+ * snd_hda_create_spdif_in_ctls - create Input SPDIF-related controls
+ * @codec: the HDA codec
+ * @nid: audio in widget NID
+ *
+ * Creates controls related with the SPDIF input.
+ * Called from each patch supporting the SPDIF in.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
+{
+ int err;
+ struct snd_kcontrol *kctl;
+ const struct snd_kcontrol_new *dig_mix;
+ int idx;
+
+ idx = find_empty_mixer_ctl_idx(codec, "IEC958 Capture Switch", 0);
+ if (idx < 0) {
+ codec_err(codec, "too many IEC958 inputs\n");
+ return -EBUSY;
+ }
+ for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) {
+ kctl = snd_ctl_new1(dig_mix, codec);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->private_value = nid;
+ err = snd_hda_ctl_add(codec, nid, kctl);
+ if (err < 0)
+ return err;
+ }
+ codec->spdif_in_enable =
+ snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DIGI_CONVERT_1, 0) &
+ AC_DIG1_ENABLE;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_create_spdif_in_ctls);
+
+/**
+ * snd_hda_codec_set_power_to_all - Set the power state to all widgets
+ * @codec: the HDA codec
+ * @fg: function group (not used now)
+ * @power_state: the power state to set (AC_PWRST_*)
+ *
+ * Set the given power state to all widgets that have the power control.
+ * If the codec has power_filter set, it evaluates the power state and
+ * filter out if it's unchanged as D3.
+ */
+void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
+ unsigned int power_state)
+{
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int state = power_state;
+ if (!(wcaps & AC_WCAP_POWER))
+ continue;
+ if (codec->power_filter) {
+ state = codec->power_filter(codec, nid, power_state);
+ if (state != power_state && power_state == AC_PWRST_D3)
+ continue;
+ }
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE,
+ state);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_power_to_all);
+
+/**
+ * snd_hda_codec_eapd_power_filter - A power filter callback for EAPD
+ * @codec: the HDA codec
+ * @nid: widget NID
+ * @power_state: power state to evalue
+ *
+ * Don't power down the widget if it controls eapd and EAPD_BTLENABLE is set.
+ * This can be used a codec power_filter callback.
+ */
+unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ if (nid == codec->core.afg || nid == codec->core.mfg)
+ return power_state;
+ if (power_state == AC_PWRST_D3 &&
+ get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_PIN &&
+ (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) {
+ int eapd = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_EAPD_BTLENABLE, 0);
+ if (eapd & 0x02)
+ return AC_PWRST_D0;
+ }
+ return power_state;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_eapd_power_filter);
+
+/*
+ * set power state of the codec, and return the power state
+ */
+static unsigned int hda_set_power_state(struct hda_codec *codec,
+ unsigned int power_state)
+{
+ hda_nid_t fg = codec->core.afg ? codec->core.afg : codec->core.mfg;
+ int count;
+ unsigned int state;
+ int flags = 0;
+
+ /* this delay seems necessary to avoid click noise at power-down */
+ if (power_state == AC_PWRST_D3) {
+ if (codec->depop_delay < 0)
+ msleep(codec_has_epss(codec) ? 10 : 100);
+ else if (codec->depop_delay > 0)
+ msleep(codec->depop_delay);
+ flags = HDA_RW_NO_RESPONSE_FALLBACK;
+ }
+
+ /* repeat power states setting at most 10 times*/
+ for (count = 0; count < 10; count++) {
+ if (codec->patch_ops.set_power_state)
+ codec->patch_ops.set_power_state(codec, fg,
+ power_state);
+ else {
+ state = power_state;
+ if (codec->power_filter)
+ state = codec->power_filter(codec, fg, state);
+ if (state == power_state || power_state != AC_PWRST_D3)
+ snd_hda_codec_read(codec, fg, flags,
+ AC_VERB_SET_POWER_STATE,
+ state);
+ snd_hda_codec_set_power_to_all(codec, fg, power_state);
+ }
+ state = snd_hda_sync_power_state(codec, fg, power_state);
+ if (!(state & AC_PWRST_ERROR))
+ break;
+ }
+
+ return state;
+}
+
+/* sync power states of all widgets;
+ * this is called at the end of codec parsing
+ */
+static void sync_power_up_states(struct hda_codec *codec)
+{
+ hda_nid_t nid;
+
+ /* don't care if no filter is used */
+ if (!codec->power_filter)
+ return;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int target;
+ if (!(wcaps & AC_WCAP_POWER))
+ continue;
+ target = codec->power_filter(codec, nid, AC_PWRST_D0);
+ if (target == AC_PWRST_D0)
+ continue;
+ if (!snd_hda_check_power_state(codec, nid, target))
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_POWER_STATE, target);
+ }
+}
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+/* execute additional init verbs */
+static void hda_exec_init_verbs(struct hda_codec *codec)
+{
+ if (codec->init_verbs.list)
+ snd_hda_sequence_write(codec, codec->init_verbs.list);
+}
+#else
+static inline void hda_exec_init_verbs(struct hda_codec *codec) {}
+#endif
+
+#ifdef CONFIG_PM
+/* update the power on/off account with the current jiffies */
+static void update_power_acct(struct hda_codec *codec, bool on)
+{
+ unsigned long delta = jiffies - codec->power_jiffies;
+
+ if (on)
+ codec->power_on_acct += delta;
+ else
+ codec->power_off_acct += delta;
+ codec->power_jiffies += delta;
+}
+
+void snd_hda_update_power_acct(struct hda_codec *codec)
+{
+ update_power_acct(codec, hda_codec_is_power_on(codec));
+}
+
+/*
+ * call suspend and power-down; used both from PM and power-save
+ * this function returns the power state in the end
+ */
+static unsigned int hda_call_codec_suspend(struct hda_codec *codec)
+{
+ unsigned int state;
+
+ snd_hdac_enter_pm(&codec->core);
+ if (codec->patch_ops.suspend)
+ codec->patch_ops.suspend(codec);
+ hda_cleanup_all_streams(codec);
+ state = hda_set_power_state(codec, AC_PWRST_D3);
+ update_power_acct(codec, true);
+ snd_hdac_leave_pm(&codec->core);
+ return state;
+}
+
+/*
+ * kick up codec; used both from PM and power-save
+ */
+static void hda_call_codec_resume(struct hda_codec *codec)
+{
+ snd_hdac_enter_pm(&codec->core);
+ if (codec->core.regmap)
+ regcache_mark_dirty(codec->core.regmap);
+
+ codec->power_jiffies = jiffies;
+
+ hda_set_power_state(codec, AC_PWRST_D0);
+ restore_shutup_pins(codec);
+ hda_exec_init_verbs(codec);
+ snd_hda_jack_set_dirty_all(codec);
+ if (codec->patch_ops.resume)
+ codec->patch_ops.resume(codec);
+ else {
+ if (codec->patch_ops.init)
+ codec->patch_ops.init(codec);
+ snd_hda_regmap_sync(codec);
+ }
+
+ if (codec->jackpoll_interval)
+ hda_jackpoll_work(&codec->jackpoll_work.work);
+ else
+ snd_hda_jack_report_sync(codec);
+ codec->core.dev.power.power_state = PMSG_ON;
+ snd_hdac_leave_pm(&codec->core);
+}
+
+static int hda_codec_runtime_suspend(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+ unsigned int state;
+
+ /* Nothing to do if card registration fails and the component driver never probes */
+ if (!codec->card)
+ return 0;
+
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ state = hda_call_codec_suspend(codec);
+ if (codec->link_down_at_suspend ||
+ (codec_has_clkstop(codec) && codec_has_epss(codec) &&
+ (state & AC_PWRST_CLK_STOP_OK)))
+ snd_hdac_codec_link_down(&codec->core);
+ codec_display_power(codec, false);
+ return 0;
+}
+
+static int hda_codec_runtime_resume(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ /* Nothing to do if card registration fails and the component driver never probes */
+ if (!codec->card)
+ return 0;
+
+ codec_display_power(codec, true);
+ snd_hdac_codec_link_up(&codec->core);
+ hda_call_codec_resume(codec);
+ pm_runtime_mark_last_busy(dev);
+ return 0;
+}
+
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PM_SLEEP
+static int hda_codec_pm_prepare(struct device *dev)
+{
+ dev->power.power_state = PMSG_SUSPEND;
+ return pm_runtime_suspended(dev);
+}
+
+static void hda_codec_pm_complete(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ /* If no other pm-functions are called between prepare() and complete() */
+ if (dev->power.power_state.event == PM_EVENT_SUSPEND)
+ dev->power.power_state = PMSG_RESUME;
+
+ if (pm_runtime_suspended(dev) && (codec->jackpoll_interval ||
+ hda_codec_need_resume(codec) || codec->forced_resume))
+ pm_request_resume(dev);
+}
+
+static int hda_codec_pm_suspend(struct device *dev)
+{
+ dev->power.power_state = PMSG_SUSPEND;
+ return pm_runtime_force_suspend(dev);
+}
+
+static int hda_codec_pm_resume(struct device *dev)
+{
+ dev->power.power_state = PMSG_RESUME;
+ return pm_runtime_force_resume(dev);
+}
+
+static int hda_codec_pm_freeze(struct device *dev)
+{
+ dev->power.power_state = PMSG_FREEZE;
+ return pm_runtime_force_suspend(dev);
+}
+
+static int hda_codec_pm_thaw(struct device *dev)
+{
+ dev->power.power_state = PMSG_THAW;
+ return pm_runtime_force_resume(dev);
+}
+
+static int hda_codec_pm_restore(struct device *dev)
+{
+ dev->power.power_state = PMSG_RESTORE;
+ return pm_runtime_force_resume(dev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+/* referred in hda_bind.c */
+const struct dev_pm_ops hda_codec_driver_pm = {
+#ifdef CONFIG_PM_SLEEP
+ .prepare = hda_codec_pm_prepare,
+ .complete = hda_codec_pm_complete,
+ .suspend = hda_codec_pm_suspend,
+ .resume = hda_codec_pm_resume,
+ .freeze = hda_codec_pm_freeze,
+ .thaw = hda_codec_pm_thaw,
+ .poweroff = hda_codec_pm_suspend,
+ .restore = hda_codec_pm_restore,
+#endif /* CONFIG_PM_SLEEP */
+ SET_RUNTIME_PM_OPS(hda_codec_runtime_suspend, hda_codec_runtime_resume,
+ NULL)
+};
+
+/*
+ * add standard channel maps if not specified
+ */
+static int add_std_chmaps(struct hda_codec *codec)
+{
+ struct hda_pcm *pcm;
+ int str, err;
+
+ list_for_each_entry(pcm, &codec->pcm_list_head, list) {
+ for (str = 0; str < 2; str++) {
+ struct hda_pcm_stream *hinfo = &pcm->stream[str];
+ struct snd_pcm_chmap *chmap;
+ const struct snd_pcm_chmap_elem *elem;
+
+ if (!pcm->pcm || pcm->own_chmap || !hinfo->substreams)
+ continue;
+ elem = hinfo->chmap ? hinfo->chmap : snd_pcm_std_chmaps;
+ err = snd_pcm_add_chmap_ctls(pcm->pcm, str, elem,
+ hinfo->channels_max,
+ 0, &chmap);
+ if (err < 0)
+ return err;
+ chmap->channel_mask = SND_PCM_CHMAP_MASK_2468;
+ }
+ }
+ return 0;
+}
+
+/* default channel maps for 2.1 speakers;
+ * since HD-audio supports only stereo, odd number channels are omitted
+ */
+const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[] = {
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+ { .channels = 4,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_LFE, SNDRV_CHMAP_LFE } },
+ { }
+};
+EXPORT_SYMBOL_GPL(snd_pcm_2_1_chmaps);
+
+int snd_hda_codec_build_controls(struct hda_codec *codec)
+{
+ int err = 0;
+ hda_exec_init_verbs(codec);
+ /* continue to initialize... */
+ if (codec->patch_ops.init)
+ err = codec->patch_ops.init(codec);
+ if (!err && codec->patch_ops.build_controls)
+ err = codec->patch_ops.build_controls(codec);
+ if (err < 0)
+ return err;
+
+ /* we create chmaps here instead of build_pcms */
+ err = add_std_chmaps(codec);
+ if (err < 0)
+ return err;
+
+ if (codec->jackpoll_interval)
+ hda_jackpoll_work(&codec->jackpoll_work.work);
+ else
+ snd_hda_jack_report_sync(codec); /* call at the last init point */
+ sync_power_up_states(codec);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_build_controls);
+
+/*
+ * PCM stuff
+ */
+static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+ return 0;
+}
+
+static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ return 0;
+}
+
+static int set_pcm_default_values(struct hda_codec *codec,
+ struct hda_pcm_stream *info)
+{
+ int err;
+
+ /* query support PCM information from the given NID */
+ if (info->nid && (!info->rates || !info->formats)) {
+ err = snd_hda_query_supported_pcm(codec, info->nid,
+ info->rates ? NULL : &info->rates,
+ info->formats ? NULL : &info->formats,
+ info->maxbps ? NULL : &info->maxbps);
+ if (err < 0)
+ return err;
+ }
+ if (info->ops.open == NULL)
+ info->ops.open = hda_pcm_default_open_close;
+ if (info->ops.close == NULL)
+ info->ops.close = hda_pcm_default_open_close;
+ if (info->ops.prepare == NULL) {
+ if (snd_BUG_ON(!info->nid))
+ return -EINVAL;
+ info->ops.prepare = hda_pcm_default_prepare;
+ }
+ if (info->ops.cleanup == NULL) {
+ if (snd_BUG_ON(!info->nid))
+ return -EINVAL;
+ info->ops.cleanup = hda_pcm_default_cleanup;
+ }
+ return 0;
+}
+
+/*
+ * codec prepare/cleanup entries
+ */
+/**
+ * snd_hda_codec_prepare - Prepare a stream
+ * @codec: the HDA codec
+ * @hinfo: PCM information
+ * @stream: stream tag to assign
+ * @format: format id to assign
+ * @substream: PCM substream to assign
+ *
+ * Calls the prepare callback set by the codec with the given arguments.
+ * Clean up the inactive streams when successful.
+ */
+int snd_hda_codec_prepare(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ unsigned int stream,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ int ret;
+ mutex_lock(&codec->bus->prepare_mutex);
+ if (hinfo->ops.prepare)
+ ret = hinfo->ops.prepare(hinfo, codec, stream, format,
+ substream);
+ else
+ ret = -ENODEV;
+ if (ret >= 0)
+ purify_inactive_streams(codec);
+ mutex_unlock(&codec->bus->prepare_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_prepare);
+
+/**
+ * snd_hda_codec_cleanup - Clean up stream resources
+ * @codec: the HDA codec
+ * @hinfo: PCM information
+ * @substream: PCM substream
+ *
+ * Calls the cleanup callback set by the codec with the given arguments.
+ */
+void snd_hda_codec_cleanup(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ struct snd_pcm_substream *substream)
+{
+ mutex_lock(&codec->bus->prepare_mutex);
+ if (hinfo->ops.cleanup)
+ hinfo->ops.cleanup(hinfo, codec, substream);
+ mutex_unlock(&codec->bus->prepare_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup);
+
+/* global */
+const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = {
+ "Audio", "SPDIF", "HDMI", "Modem"
+};
+
+/*
+ * get the empty PCM device number to assign
+ */
+static int get_empty_pcm_device(struct hda_bus *bus, unsigned int type)
+{
+ /* audio device indices; not linear to keep compatibility */
+ /* assigned to static slots up to dev#10; if more needed, assign
+ * the later slot dynamically (when CONFIG_SND_DYNAMIC_MINORS=y)
+ */
+ static const int audio_idx[HDA_PCM_NTYPES][5] = {
+ [HDA_PCM_TYPE_AUDIO] = { 0, 2, 4, 5, -1 },
+ [HDA_PCM_TYPE_SPDIF] = { 1, -1 },
+ [HDA_PCM_TYPE_HDMI] = { 3, 7, 8, 9, -1 },
+ [HDA_PCM_TYPE_MODEM] = { 6, -1 },
+ };
+ int i;
+
+ if (type >= HDA_PCM_NTYPES) {
+ dev_err(bus->card->dev, "Invalid PCM type %d\n", type);
+ return -EINVAL;
+ }
+
+ for (i = 0; audio_idx[type][i] >= 0; i++) {
+#ifndef CONFIG_SND_DYNAMIC_MINORS
+ if (audio_idx[type][i] >= 8)
+ break;
+#endif
+ if (!test_and_set_bit(audio_idx[type][i], bus->pcm_dev_bits))
+ return audio_idx[type][i];
+ }
+
+#ifdef CONFIG_SND_DYNAMIC_MINORS
+ /* non-fixed slots starting from 10 */
+ for (i = 10; i < 32; i++) {
+ if (!test_and_set_bit(i, bus->pcm_dev_bits))
+ return i;
+ }
+#endif
+
+ dev_warn(bus->card->dev, "Too many %s devices\n",
+ snd_hda_pcm_type_name[type]);
+#ifndef CONFIG_SND_DYNAMIC_MINORS
+ dev_warn(bus->card->dev,
+ "Consider building the kernel with CONFIG_SND_DYNAMIC_MINORS=y\n");
+#endif
+ return -EAGAIN;
+}
+
+/* call build_pcms ops of the given codec and set up the default parameters */
+int snd_hda_codec_parse_pcms(struct hda_codec *codec)
+{
+ struct hda_pcm *cpcm;
+ int err;
+
+ if (!list_empty(&codec->pcm_list_head))
+ return 0; /* already parsed */
+
+ if (!codec->patch_ops.build_pcms)
+ return 0;
+
+ err = codec->patch_ops.build_pcms(codec);
+ if (err < 0) {
+ codec_err(codec, "cannot build PCMs for #%d (error %d)\n",
+ codec->core.addr, err);
+ return err;
+ }
+
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ struct hda_pcm_stream *info = &cpcm->stream[stream];
+
+ if (!info->substreams)
+ continue;
+ err = set_pcm_default_values(codec, info);
+ if (err < 0) {
+ codec_warn(codec,
+ "fail to setup default for PCM %s\n",
+ cpcm->name);
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_parse_pcms);
+
+/* assign all PCMs of the given codec */
+int snd_hda_codec_build_pcms(struct hda_codec *codec)
+{
+ struct hda_bus *bus = codec->bus;
+ struct hda_pcm *cpcm;
+ int dev, err;
+
+ err = snd_hda_codec_parse_pcms(codec);
+ if (err < 0)
+ return err;
+
+ /* attach a new PCM streams */
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ if (cpcm->pcm)
+ continue; /* already attached */
+ if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
+ continue; /* no substreams assigned */
+
+ dev = get_empty_pcm_device(bus, cpcm->pcm_type);
+ if (dev < 0) {
+ cpcm->device = SNDRV_PCM_INVALID_DEVICE;
+ continue; /* no fatal error */
+ }
+ cpcm->device = dev;
+ err = snd_hda_attach_pcm_stream(bus, codec, cpcm);
+ if (err < 0) {
+ codec_err(codec,
+ "cannot attach PCM stream %d for codec #%d\n",
+ dev, codec->core.addr);
+ continue; /* no fatal error */
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * snd_hda_add_new_ctls - create controls from the array
+ * @codec: the HDA codec
+ * @knew: the array of struct snd_kcontrol_new
+ *
+ * This helper function creates and add new controls in the given array.
+ * The array must be terminated with an empty entry as terminator.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_add_new_ctls(struct hda_codec *codec,
+ const struct snd_kcontrol_new *knew)
+{
+ int err;
+
+ for (; knew->name; knew++) {
+ struct snd_kcontrol *kctl;
+ int addr = 0, idx = 0;
+ if (knew->iface == (__force snd_ctl_elem_iface_t)-1)
+ continue; /* skip this codec private value */
+ for (;;) {
+ kctl = snd_ctl_new1(knew, codec);
+ if (!kctl)
+ return -ENOMEM;
+ if (addr > 0)
+ kctl->id.device = addr;
+ if (idx > 0)
+ kctl->id.index = idx;
+ err = snd_hda_ctl_add(codec, 0, kctl);
+ if (!err)
+ break;
+ /* try first with another device index corresponding to
+ * the codec addr; if it still fails (or it's the
+ * primary codec), then try another control index
+ */
+ if (!addr && codec->core.addr)
+ addr = codec->core.addr;
+ else if (!idx && !knew->index) {
+ idx = find_empty_mixer_ctl_idx(codec,
+ knew->name, 0);
+ if (idx <= 0)
+ return err;
+ } else
+ return err;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_new_ctls);
+
+#ifdef CONFIG_PM
+static void codec_set_power_save(struct hda_codec *codec, int delay)
+{
+ struct device *dev = hda_codec_dev(codec);
+
+ if (delay == 0 && codec->auto_runtime_pm)
+ delay = 3000;
+
+ if (delay > 0) {
+ pm_runtime_set_autosuspend_delay(dev, delay);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_allow(dev);
+ if (!pm_runtime_suspended(dev))
+ pm_runtime_mark_last_busy(dev);
+ } else {
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_forbid(dev);
+ }
+}
+
+/**
+ * snd_hda_set_power_save - reprogram autosuspend for the given delay
+ * @bus: HD-audio bus
+ * @delay: autosuspend delay in msec, 0 = off
+ *
+ * Synchronize the runtime PM autosuspend state from the power_save option.
+ */
+void snd_hda_set_power_save(struct hda_bus *bus, int delay)
+{
+ struct hda_codec *c;
+
+ list_for_each_codec(c, bus)
+ codec_set_power_save(c, delay);
+}
+EXPORT_SYMBOL_GPL(snd_hda_set_power_save);
+
+/**
+ * snd_hda_check_amp_list_power - Check the amp list and update the power
+ * @codec: HD-audio codec
+ * @check: the object containing an AMP list and the status
+ * @nid: NID to check / update
+ *
+ * Check whether the given NID is in the amp list. If it's in the list,
+ * check the current AMP status, and update the power-status according
+ * to the mute status.
+ *
+ * This function is supposed to be set or called from the check_power_status
+ * patch ops.
+ */
+int snd_hda_check_amp_list_power(struct hda_codec *codec,
+ struct hda_loopback_check *check,
+ hda_nid_t nid)
+{
+ const struct hda_amp_list *p;
+ int ch, v;
+
+ if (!check->amplist)
+ return 0;
+ for (p = check->amplist; p->nid; p++) {
+ if (p->nid == nid)
+ break;
+ }
+ if (!p->nid)
+ return 0; /* nothing changed */
+
+ for (p = check->amplist; p->nid; p++) {
+ for (ch = 0; ch < 2; ch++) {
+ v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir,
+ p->idx);
+ if (!(v & HDA_AMP_MUTE) && v > 0) {
+ if (!check->power_on) {
+ check->power_on = 1;
+ snd_hda_power_up_pm(codec);
+ }
+ return 1;
+ }
+ }
+ }
+ if (check->power_on) {
+ check->power_on = 0;
+ snd_hda_power_down_pm(codec);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_check_amp_list_power);
+#endif
+
+/*
+ * input MUX helper
+ */
+
+/**
+ * snd_hda_input_mux_info_info - Info callback helper for the input-mux enum
+ * @imux: imux helper object
+ * @uinfo: pointer to get/store the data
+ */
+int snd_hda_input_mux_info(const struct hda_input_mux *imux,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int index;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = imux->num_items;
+ if (!imux->num_items)
+ return 0;
+ index = uinfo->value.enumerated.item;
+ if (index >= imux->num_items)
+ index = imux->num_items - 1;
+ strcpy(uinfo->value.enumerated.name, imux->items[index].label);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_input_mux_info);
+
+/**
+ * snd_hda_input_mux_info_put - Put callback helper for the input-mux enum
+ * @codec: the HDA codec
+ * @imux: imux helper object
+ * @ucontrol: pointer to get/store the data
+ * @nid: input mux NID
+ * @cur_val: pointer to get/store the current imux value
+ */
+int snd_hda_input_mux_put(struct hda_codec *codec,
+ const struct hda_input_mux *imux,
+ struct snd_ctl_elem_value *ucontrol,
+ hda_nid_t nid,
+ unsigned int *cur_val)
+{
+ unsigned int idx;
+
+ if (!imux->num_items)
+ return 0;
+ idx = ucontrol->value.enumerated.item[0];
+ if (idx >= imux->num_items)
+ idx = imux->num_items - 1;
+ if (*cur_val == idx)
+ return 0;
+ snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_CONNECT_SEL,
+ imux->items[idx].index);
+ *cur_val = idx;
+ return 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_input_mux_put);
+
+
+/**
+ * snd_hda_enum_helper_info - Helper for simple enum ctls
+ * @kcontrol: ctl element
+ * @uinfo: pointer to get/store the data
+ * @num_items: number of enum items
+ * @texts: enum item string array
+ *
+ * process kcontrol info callback of a simple string enum array
+ * when @num_items is 0 or @texts is NULL, assume a boolean enum array
+ */
+int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo,
+ int num_items, const char * const *texts)
+{
+ static const char * const texts_default[] = {
+ "Disabled", "Enabled"
+ };
+
+ if (!texts || !num_items) {
+ num_items = 2;
+ texts = texts_default;
+ }
+
+ return snd_ctl_enum_info(uinfo, 1, num_items, texts);
+}
+EXPORT_SYMBOL_GPL(snd_hda_enum_helper_info);
+
+/*
+ * Multi-channel / digital-out PCM helper functions
+ */
+
+/* setup SPDIF output stream */
+static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int stream_tag, unsigned int format)
+{
+ struct hda_spdif_out *spdif;
+ unsigned int curr_fmt;
+ bool reset;
+
+ spdif = snd_hda_spdif_out_of_nid(codec, nid);
+ /* Add sanity check to pass klockwork check.
+ * This should never happen.
+ */
+ if (WARN_ON(spdif == NULL))
+ return;
+
+ curr_fmt = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_STREAM_FORMAT, 0);
+ reset = codec->spdif_status_reset &&
+ (spdif->ctls & AC_DIG1_ENABLE) &&
+ curr_fmt != format;
+
+ /* turn off SPDIF if needed; otherwise the IEC958 bits won't be
+ updated */
+ if (reset)
+ set_dig_out_convert(codec, nid,
+ spdif->ctls & ~AC_DIG1_ENABLE & 0xff,
+ -1);
+ snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+ if (codec->follower_dig_outs) {
+ const hda_nid_t *d;
+ for (d = codec->follower_dig_outs; *d; d++)
+ snd_hda_codec_setup_stream(codec, *d, stream_tag, 0,
+ format);
+ }
+ /* turn on again (if needed) */
+ if (reset)
+ set_dig_out_convert(codec, nid,
+ spdif->ctls & 0xff, -1);
+}
+
+static void cleanup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid)
+{
+ snd_hda_codec_cleanup_stream(codec, nid);
+ if (codec->follower_dig_outs) {
+ const hda_nid_t *d;
+ for (d = codec->follower_dig_outs; *d; d++)
+ snd_hda_codec_cleanup_stream(codec, *d);
+ }
+}
+
+/**
+ * snd_hda_multi_out_dig_open - open the digital out in the exclusive mode
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_dig_open(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ mutex_lock(&codec->spdif_mutex);
+ if (mout->dig_out_used == HDA_DIG_ANALOG_DUP)
+ /* already opened as analog dup; reset it once */
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ mout->dig_out_used = HDA_DIG_EXCLUSIVE;
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_open);
+
+/**
+ * snd_hda_multi_out_dig_prepare - prepare the digital out stream
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ * @stream_tag: stream tag to assign
+ * @format: format id to assign
+ * @substream: PCM substream to assign
+ */
+int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ mutex_lock(&codec->spdif_mutex);
+ setup_dig_out_stream(codec, mout->dig_out_nid, stream_tag, format);
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_prepare);
+
+/**
+ * snd_hda_multi_out_dig_cleanup - clean-up the digital out stream
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ mutex_lock(&codec->spdif_mutex);
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_cleanup);
+
+/**
+ * snd_hda_multi_out_dig_close - release the digital out stream
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_dig_close(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ mutex_lock(&codec->spdif_mutex);
+ mout->dig_out_used = 0;
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_close);
+
+/**
+ * snd_hda_multi_out_analog_open - open analog outputs
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ * @substream: PCM substream to assign
+ * @hinfo: PCM information to assign
+ *
+ * Open analog outputs and set up the hw-constraints.
+ * If the digital outputs can be opened as follower, open the digital
+ * outputs, too.
+ */
+int snd_hda_multi_out_analog_open(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ struct snd_pcm_substream *substream,
+ struct hda_pcm_stream *hinfo)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ runtime->hw.channels_max = mout->max_channels;
+ if (mout->dig_out_nid) {
+ if (!mout->analog_rates) {
+ mout->analog_rates = hinfo->rates;
+ mout->analog_formats = hinfo->formats;
+ mout->analog_maxbps = hinfo->maxbps;
+ } else {
+ runtime->hw.rates = mout->analog_rates;
+ runtime->hw.formats = mout->analog_formats;
+ hinfo->maxbps = mout->analog_maxbps;
+ }
+ if (!mout->spdif_rates) {
+ snd_hda_query_supported_pcm(codec, mout->dig_out_nid,
+ &mout->spdif_rates,
+ &mout->spdif_formats,
+ &mout->spdif_maxbps);
+ }
+ mutex_lock(&codec->spdif_mutex);
+ if (mout->share_spdif) {
+ if ((runtime->hw.rates & mout->spdif_rates) &&
+ (runtime->hw.formats & mout->spdif_formats)) {
+ runtime->hw.rates &= mout->spdif_rates;
+ runtime->hw.formats &= mout->spdif_formats;
+ if (mout->spdif_maxbps < hinfo->maxbps)
+ hinfo->maxbps = mout->spdif_maxbps;
+ } else {
+ mout->share_spdif = 0;
+ /* FIXME: need notify? */
+ }
+ }
+ mutex_unlock(&codec->spdif_mutex);
+ }
+ return snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open);
+
+/**
+ * snd_hda_multi_out_analog_prepare - Preapre the analog outputs.
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ * @stream_tag: stream tag to assign
+ * @format: format id to assign
+ * @substream: PCM substream to assign
+ *
+ * Set up the i/o for analog out.
+ * When the digital out is available, copy the front out to digital out, too.
+ */
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ const hda_nid_t *nids = mout->dac_nids;
+ int chs = substream->runtime->channels;
+ struct hda_spdif_out *spdif;
+ int i;
+
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_hda_spdif_out_of_nid(codec, mout->dig_out_nid);
+ if (mout->dig_out_nid && mout->share_spdif &&
+ mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
+ if (chs == 2 && spdif != NULL &&
+ snd_hda_is_supported_format(codec, mout->dig_out_nid,
+ format) &&
+ !(spdif->status & IEC958_AES0_NONAUDIO)) {
+ mout->dig_out_used = HDA_DIG_ANALOG_DUP;
+ setup_dig_out_stream(codec, mout->dig_out_nid,
+ stream_tag, format);
+ } else {
+ mout->dig_out_used = 0;
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ }
+ }
+ mutex_unlock(&codec->spdif_mutex);
+
+ /* front */
+ snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag,
+ 0, format);
+ if (!mout->no_share_stream &&
+ mout->hp_nid && mout->hp_nid != nids[HDA_FRONT])
+ /* headphone out will just decode front left/right (stereo) */
+ snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag,
+ 0, format);
+ /* extra outputs copied from front */
+ for (i = 0; i < ARRAY_SIZE(mout->hp_out_nid); i++)
+ if (!mout->no_share_stream && mout->hp_out_nid[i])
+ snd_hda_codec_setup_stream(codec,
+ mout->hp_out_nid[i],
+ stream_tag, 0, format);
+
+ /* surrounds */
+ for (i = 1; i < mout->num_dacs; i++) {
+ if (chs >= (i + 1) * 2) /* independent out */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+ i * 2, format);
+ else if (!mout->no_share_stream) /* copy front */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+ 0, format);
+ }
+
+ /* extra surrounds */
+ for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) {
+ int ch = 0;
+ if (!mout->extra_out_nid[i])
+ break;
+ if (chs >= (i + 1) * 2)
+ ch = i * 2;
+ else if (!mout->no_share_stream)
+ break;
+ snd_hda_codec_setup_stream(codec, mout->extra_out_nid[i],
+ stream_tag, ch, format);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare);
+
+/**
+ * snd_hda_multi_out_analog_cleanup - clean up the setting for analog out
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ const hda_nid_t *nids = mout->dac_nids;
+ int i;
+
+ for (i = 0; i < mout->num_dacs; i++)
+ snd_hda_codec_cleanup_stream(codec, nids[i]);
+ if (mout->hp_nid)
+ snd_hda_codec_cleanup_stream(codec, mout->hp_nid);
+ for (i = 0; i < ARRAY_SIZE(mout->hp_out_nid); i++)
+ if (mout->hp_out_nid[i])
+ snd_hda_codec_cleanup_stream(codec,
+ mout->hp_out_nid[i]);
+ for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
+ if (mout->extra_out_nid[i])
+ snd_hda_codec_cleanup_stream(codec,
+ mout->extra_out_nid[i]);
+ mutex_lock(&codec->spdif_mutex);
+ if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ mout->dig_out_used = 0;
+ }
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_cleanup);
+
+/**
+ * snd_hda_get_default_vref - Get the default (mic) VREF pin bits
+ * @codec: the HDA codec
+ * @pin: referred pin NID
+ *
+ * Guess the suitable VREF pin bits to be set as the pin-control value.
+ * Note: the function doesn't set the AC_PINCTL_IN_EN bit.
+ */
+unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin)
+{
+ unsigned int pincap;
+ unsigned int oldval;
+ oldval = snd_hda_codec_read(codec, pin, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ pincap = snd_hda_query_pin_caps(codec, pin);
+ pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ /* Exception: if the default pin setup is vref50, we give it priority */
+ if ((pincap & AC_PINCAP_VREF_80) && oldval != PIN_VREF50)
+ return AC_PINCTL_VREF_80;
+ else if (pincap & AC_PINCAP_VREF_50)
+ return AC_PINCTL_VREF_50;
+ else if (pincap & AC_PINCAP_VREF_100)
+ return AC_PINCTL_VREF_100;
+ else if (pincap & AC_PINCAP_VREF_GRD)
+ return AC_PINCTL_VREF_GRD;
+ return AC_PINCTL_VREF_HIZ;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_default_vref);
+
+/**
+ * snd_hda_correct_pin_ctl - correct the pin ctl value for matching with the pin cap
+ * @codec: the HDA codec
+ * @pin: referred pin NID
+ * @val: pin ctl value to audit
+ */
+unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec,
+ hda_nid_t pin, unsigned int val)
+{
+ static const unsigned int cap_lists[][2] = {
+ { AC_PINCTL_VREF_100, AC_PINCAP_VREF_100 },
+ { AC_PINCTL_VREF_80, AC_PINCAP_VREF_80 },
+ { AC_PINCTL_VREF_50, AC_PINCAP_VREF_50 },
+ { AC_PINCTL_VREF_GRD, AC_PINCAP_VREF_GRD },
+ };
+ unsigned int cap;
+
+ if (!val)
+ return 0;
+ cap = snd_hda_query_pin_caps(codec, pin);
+ if (!cap)
+ return val; /* don't know what to do... */
+
+ if (val & AC_PINCTL_OUT_EN) {
+ if (!(cap & AC_PINCAP_OUT))
+ val &= ~(AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
+ else if ((val & AC_PINCTL_HP_EN) && !(cap & AC_PINCAP_HP_DRV))
+ val &= ~AC_PINCTL_HP_EN;
+ }
+
+ if (val & AC_PINCTL_IN_EN) {
+ if (!(cap & AC_PINCAP_IN))
+ val &= ~(AC_PINCTL_IN_EN | AC_PINCTL_VREFEN);
+ else {
+ unsigned int vcap, vref;
+ int i;
+ vcap = (cap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ vref = val & AC_PINCTL_VREFEN;
+ for (i = 0; i < ARRAY_SIZE(cap_lists); i++) {
+ if (vref == cap_lists[i][0] &&
+ !(vcap & cap_lists[i][1])) {
+ if (i == ARRAY_SIZE(cap_lists) - 1)
+ vref = AC_PINCTL_VREF_HIZ;
+ else
+ vref = cap_lists[i + 1][0];
+ }
+ }
+ val &= ~AC_PINCTL_VREFEN;
+ val |= vref;
+ }
+ }
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(snd_hda_correct_pin_ctl);
+
+/**
+ * _snd_hda_pin_ctl - Helper to set pin ctl value
+ * @codec: the HDA codec
+ * @pin: referred pin NID
+ * @val: pin control value to set
+ * @cached: access over codec pinctl cache or direct write
+ *
+ * This function is a helper to set a pin ctl value more safely.
+ * It corrects the pin ctl value via snd_hda_correct_pin_ctl(), stores the
+ * value in pin target array via snd_hda_codec_set_pin_target(), then
+ * actually writes the value via either snd_hda_codec_write_cache() or
+ * snd_hda_codec_write() depending on @cached flag.
+ */
+int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val, bool cached)
+{
+ val = snd_hda_correct_pin_ctl(codec, pin, val);
+ snd_hda_codec_set_pin_target(codec, pin, val);
+ if (cached)
+ return snd_hda_codec_write_cache(codec, pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val);
+ else
+ return snd_hda_codec_write(codec, pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val);
+}
+EXPORT_SYMBOL_GPL(_snd_hda_set_pin_ctl);
+
+/**
+ * snd_hda_add_imux_item - Add an item to input_mux
+ * @codec: the HDA codec
+ * @imux: imux helper object
+ * @label: the name of imux item to assign
+ * @index: index number of imux item to assign
+ * @type_idx: pointer to store the resultant label index
+ *
+ * When the same label is used already in the existing items, the number
+ * suffix is appended to the label. This label index number is stored
+ * to type_idx when non-NULL pointer is given.
+ */
+int snd_hda_add_imux_item(struct hda_codec *codec,
+ struct hda_input_mux *imux, const char *label,
+ int index, int *type_idx)
+{
+ int i, label_idx = 0;
+ if (imux->num_items >= HDA_MAX_NUM_INPUTS) {
+ codec_err(codec, "hda_codec: Too many imux items!\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < imux->num_items; i++) {
+ if (!strncmp(label, imux->items[i].label, strlen(label)))
+ label_idx++;
+ }
+ if (type_idx)
+ *type_idx = label_idx;
+ if (label_idx > 0)
+ snprintf(imux->items[imux->num_items].label,
+ sizeof(imux->items[imux->num_items].label),
+ "%s %d", label, label_idx);
+ else
+ strlcpy(imux->items[imux->num_items].label, label,
+ sizeof(imux->items[imux->num_items].label));
+ imux->items[imux->num_items].index = index;
+ imux->num_items++;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_imux_item);
+
+/**
+ * snd_hda_bus_reset_codecs - Reset the bus
+ * @bus: HD-audio bus
+ */
+void snd_hda_bus_reset_codecs(struct hda_bus *bus)
+{
+ struct hda_codec *codec;
+
+ list_for_each_codec(codec, bus) {
+ /* FIXME: maybe a better way needed for forced reset */
+ if (current_work() != &codec->jackpoll_work.work)
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+#ifdef CONFIG_PM
+ if (hda_codec_is_power_on(codec)) {
+ hda_call_codec_suspend(codec);
+ hda_call_codec_resume(codec);
+ }
+#endif
+ }
+}
+
+/**
+ * snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer
+ * @pcm: PCM caps bits
+ * @buf: the string buffer to write
+ * @buflen: the max buffer length
+ *
+ * used by hda_proc.c and hda_eld.c
+ */
+void snd_print_pcm_bits(int pcm, char *buf, int buflen)
+{
+ static const unsigned int bits[] = { 8, 16, 20, 24, 32 };
+ int i, j;
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++)
+ if (pcm & (AC_SUPPCM_BITS_8 << i))
+ j += scnprintf(buf + j, buflen - j, " %d", bits[i]);
+
+ buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_GPL(snd_print_pcm_bits);
+
+MODULE_DESCRIPTION("HDA codec core");
+MODULE_LICENSE("GPL");
diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c
new file mode 100644
index 000000000..ea76395d7
--- /dev/null
+++ b/sound/pci/hda/hda_controller.c
@@ -0,0 +1,1337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Implementation of primary alsa driver code base for Intel HD Audio.
+ *
+ * Copyright(c) 2004 Intel Corporation. All rights reserved.
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ * PeiSen Hou <pshou@realtek.com.tw>
+ */
+
+#include <linux/clocksource.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#ifdef CONFIG_X86
+/* for art-tsc conversion */
+#include <asm/tsc.h>
+#endif
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "hda_controller.h"
+#include "hda_local.h"
+
+#define CREATE_TRACE_POINTS
+#include "hda_controller_trace.h"
+
+/* DSP lock helpers */
+#define dsp_lock(dev) snd_hdac_dsp_lock(azx_stream(dev))
+#define dsp_unlock(dev) snd_hdac_dsp_unlock(azx_stream(dev))
+#define dsp_is_locked(dev) snd_hdac_stream_is_locked(azx_stream(dev))
+
+/* assign a stream for the PCM */
+static inline struct azx_dev *
+azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *s;
+
+ s = snd_hdac_stream_assign(azx_bus(chip), substream);
+ if (!s)
+ return NULL;
+ return stream_to_azx_dev(s);
+}
+
+/* release the assigned stream */
+static inline void azx_release_device(struct azx_dev *azx_dev)
+{
+ snd_hdac_stream_release(azx_stream(azx_dev));
+}
+
+static inline struct hda_pcm_stream *
+to_hda_pcm_stream(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ return &apcm->info->stream[substream->stream];
+}
+
+static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream,
+ u64 nsec)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ u64 codec_frames, codec_nsecs;
+
+ if (!hinfo->ops.get_delay)
+ return nsec;
+
+ codec_frames = hinfo->ops.get_delay(hinfo, apcm->codec, substream);
+ codec_nsecs = div_u64(codec_frames * 1000000000LL,
+ substream->runtime->rate);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return nsec + codec_nsecs;
+
+ return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0;
+}
+
+/*
+ * PCM ops
+ */
+
+static int azx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+
+ trace_azx_pcm_close(chip, azx_dev);
+ mutex_lock(&chip->open_mutex);
+ azx_release_device(azx_dev);
+ if (hinfo->ops.close)
+ hinfo->ops.close(hinfo, apcm->codec, substream);
+ snd_hda_power_down(apcm->codec);
+ mutex_unlock(&chip->open_mutex);
+ snd_hda_codec_pcm_put(apcm->info);
+ return 0;
+}
+
+static int azx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ int ret = 0;
+
+ trace_azx_pcm_hw_params(chip, azx_dev);
+ dsp_lock(azx_dev);
+ if (dsp_is_locked(azx_dev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ azx_dev->core.bufsize = 0;
+ azx_dev->core.period_bytes = 0;
+ azx_dev->core.format_val = 0;
+
+unlock:
+ dsp_unlock(azx_dev);
+ return ret;
+}
+
+static int azx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+
+ /* reset BDL address */
+ dsp_lock(azx_dev);
+ if (!dsp_is_locked(azx_dev))
+ snd_hdac_stream_cleanup(azx_stream(azx_dev));
+
+ snd_hda_codec_cleanup(apcm->codec, hinfo, substream);
+
+ azx_stream(azx_dev)->prepared = 0;
+ dsp_unlock(azx_dev);
+ return 0;
+}
+
+static int azx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int format_val, stream_tag;
+ int err;
+ struct hda_spdif_out *spdif =
+ snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid);
+ unsigned short ctls = spdif ? spdif->ctls : 0;
+
+ trace_azx_pcm_prepare(chip, azx_dev);
+ dsp_lock(azx_dev);
+ if (dsp_is_locked(azx_dev)) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ snd_hdac_stream_reset(azx_stream(azx_dev));
+ format_val = snd_hdac_calc_stream_format(runtime->rate,
+ runtime->channels,
+ runtime->format,
+ hinfo->maxbps,
+ ctls);
+ if (!format_val) {
+ dev_err(chip->card->dev,
+ "invalid format_val, rate=%d, ch=%d, format=%d\n",
+ runtime->rate, runtime->channels, runtime->format);
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = snd_hdac_stream_set_params(azx_stream(azx_dev), format_val);
+ if (err < 0)
+ goto unlock;
+
+ snd_hdac_stream_setup(azx_stream(azx_dev));
+
+ stream_tag = azx_dev->core.stream_tag;
+ /* CA-IBG chips need the playback stream starting from 1 */
+ if ((chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) &&
+ stream_tag > chip->capture_streams)
+ stream_tag -= chip->capture_streams;
+ err = snd_hda_codec_prepare(apcm->codec, hinfo, stream_tag,
+ azx_dev->core.format_val, substream);
+
+ unlock:
+ if (!err)
+ azx_stream(azx_dev)->prepared = 1;
+ dsp_unlock(azx_dev);
+ return err;
+}
+
+static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct hdac_bus *bus = azx_bus(chip);
+ struct azx_dev *azx_dev;
+ struct snd_pcm_substream *s;
+ struct hdac_stream *hstr;
+ bool start;
+ int sbits = 0;
+ int sync_reg;
+
+ azx_dev = get_azx_dev(substream);
+ trace_azx_pcm_trigger(chip, azx_dev, cmd);
+
+ hstr = azx_stream(azx_dev);
+ if (chip->driver_caps & AZX_DCAPS_OLD_SSYNC)
+ sync_reg = AZX_REG_OLD_SSYNC;
+ else
+ sync_reg = AZX_REG_SSYNC;
+
+ if (dsp_is_locked(azx_dev) || !hstr->prepared)
+ return -EPIPE;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ start = true;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ start = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ sbits |= 1 << azx_dev->core.index;
+ snd_pcm_trigger_done(s, substream);
+ }
+
+ spin_lock(&bus->reg_lock);
+
+ /* first, set SYNC bits of corresponding streams */
+ snd_hdac_stream_sync_trigger(hstr, true, sbits, sync_reg);
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ if (start) {
+ azx_dev->insufficient = 1;
+ snd_hdac_stream_start(azx_stream(azx_dev), true);
+ } else {
+ snd_hdac_stream_stop(azx_stream(azx_dev));
+ }
+ }
+ spin_unlock(&bus->reg_lock);
+
+ snd_hdac_stream_sync(hstr, start, sbits);
+
+ spin_lock(&bus->reg_lock);
+ /* reset SYNC bits */
+ snd_hdac_stream_sync_trigger(hstr, false, sbits, sync_reg);
+ if (start)
+ snd_hdac_stream_timecounter_init(hstr, sbits);
+ spin_unlock(&bus->reg_lock);
+ return 0;
+}
+
+unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev)
+{
+ return snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev));
+}
+EXPORT_SYMBOL_GPL(azx_get_pos_lpib);
+
+unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev)
+{
+ return snd_hdac_stream_get_pos_posbuf(azx_stream(azx_dev));
+}
+EXPORT_SYMBOL_GPL(azx_get_pos_posbuf);
+
+unsigned int azx_get_position(struct azx *chip,
+ struct azx_dev *azx_dev)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ unsigned int pos;
+ int stream = substream->stream;
+ int delay = 0;
+
+ if (chip->get_position[stream])
+ pos = chip->get_position[stream](chip, azx_dev);
+ else /* use the position buffer as default */
+ pos = azx_get_pos_posbuf(chip, azx_dev);
+
+ if (pos >= azx_dev->core.bufsize)
+ pos = 0;
+
+ if (substream->runtime) {
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+
+ if (chip->get_delay[stream])
+ delay += chip->get_delay[stream](chip, azx_dev, pos);
+ if (hinfo->ops.get_delay)
+ delay += hinfo->ops.get_delay(hinfo, apcm->codec,
+ substream);
+ substream->runtime->delay = delay;
+ }
+
+ trace_azx_get_position(chip, azx_dev, pos, delay);
+ return pos;
+}
+EXPORT_SYMBOL_GPL(azx_get_position);
+
+static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ return bytes_to_frames(substream->runtime,
+ azx_get_position(chip, azx_dev));
+}
+
+/*
+ * azx_scale64: Scale base by mult/div while not overflowing sanely
+ *
+ * Derived from scale64_check_overflow in kernel/time/timekeeping.c
+ *
+ * The tmestamps for a 48Khz stream can overflow after (2^64/10^9)/48K which
+ * is about 384307 ie ~4.5 days.
+ *
+ * This scales the calculation so that overflow will happen but after 2^64 /
+ * 48000 secs, which is pretty large!
+ *
+ * In caln below:
+ * base may overflow, but since there isn’t any additional division
+ * performed on base it’s OK
+ * rem can’t overflow because both are 32-bit values
+ */
+
+#ifdef CONFIG_X86
+static u64 azx_scale64(u64 base, u32 num, u32 den)
+{
+ u64 rem;
+
+ rem = do_div(base, den);
+
+ base *= num;
+ rem *= num;
+
+ do_div(rem, den);
+
+ return base + rem;
+}
+
+static int azx_get_sync_time(ktime_t *device,
+ struct system_counterval_t *system, void *ctx)
+{
+ struct snd_pcm_substream *substream = ctx;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct snd_pcm_runtime *runtime;
+ u64 ll_counter, ll_counter_l, ll_counter_h;
+ u64 tsc_counter, tsc_counter_l, tsc_counter_h;
+ u32 wallclk_ctr, wallclk_cycles;
+ bool direction;
+ u32 dma_select;
+ u32 timeout;
+ u32 retry_count = 0;
+
+ runtime = substream->runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ direction = 1;
+ else
+ direction = 0;
+
+ /* 0th stream tag is not used, so DMA ch 0 is for 1st stream tag */
+ do {
+ timeout = 100;
+ dma_select = (direction << GTSCC_CDMAS_DMA_DIR_SHIFT) |
+ (azx_dev->core.stream_tag - 1);
+ snd_hdac_chip_writel(azx_bus(chip), GTSCC, dma_select);
+
+ /* Enable the capture */
+ snd_hdac_chip_updatel(azx_bus(chip), GTSCC, 0, GTSCC_TSCCI_MASK);
+
+ while (timeout) {
+ if (snd_hdac_chip_readl(azx_bus(chip), GTSCC) &
+ GTSCC_TSCCD_MASK)
+ break;
+
+ timeout--;
+ }
+
+ if (!timeout) {
+ dev_err(chip->card->dev, "GTSCC capture Timedout!\n");
+ return -EIO;
+ }
+
+ /* Read wall clock counter */
+ wallclk_ctr = snd_hdac_chip_readl(azx_bus(chip), WALFCC);
+
+ /* Read TSC counter */
+ tsc_counter_l = snd_hdac_chip_readl(azx_bus(chip), TSCCL);
+ tsc_counter_h = snd_hdac_chip_readl(azx_bus(chip), TSCCU);
+
+ /* Read Link counter */
+ ll_counter_l = snd_hdac_chip_readl(azx_bus(chip), LLPCL);
+ ll_counter_h = snd_hdac_chip_readl(azx_bus(chip), LLPCU);
+
+ /* Ack: registers read done */
+ snd_hdac_chip_writel(azx_bus(chip), GTSCC, GTSCC_TSCCD_SHIFT);
+
+ tsc_counter = (tsc_counter_h << TSCCU_CCU_SHIFT) |
+ tsc_counter_l;
+
+ ll_counter = (ll_counter_h << LLPC_CCU_SHIFT) | ll_counter_l;
+ wallclk_cycles = wallclk_ctr & WALFCC_CIF_MASK;
+
+ /*
+ * An error occurs near frame "rollover". The clocks in
+ * frame value indicates whether this error may have
+ * occurred. Here we use the value of 10 i.e.,
+ * HDA_MAX_CYCLE_OFFSET
+ */
+ if (wallclk_cycles < HDA_MAX_CYCLE_VALUE - HDA_MAX_CYCLE_OFFSET
+ && wallclk_cycles > HDA_MAX_CYCLE_OFFSET)
+ break;
+
+ /*
+ * Sleep before we read again, else we may again get
+ * value near to MAX_CYCLE. Try to sleep for different
+ * amount of time so we dont hit the same number again
+ */
+ udelay(retry_count++);
+
+ } while (retry_count != HDA_MAX_CYCLE_READ_RETRY);
+
+ if (retry_count == HDA_MAX_CYCLE_READ_RETRY) {
+ dev_err_ratelimited(chip->card->dev,
+ "Error in WALFCC cycle count\n");
+ return -EIO;
+ }
+
+ *device = ns_to_ktime(azx_scale64(ll_counter,
+ NSEC_PER_SEC, runtime->rate));
+ *device = ktime_add_ns(*device, (wallclk_cycles * NSEC_PER_SEC) /
+ ((HDA_MAX_CYCLE_VALUE + 1) * runtime->rate));
+
+ *system = convert_art_to_tsc(tsc_counter);
+
+ return 0;
+}
+
+#else
+static int azx_get_sync_time(ktime_t *device,
+ struct system_counterval_t *system, void *ctx)
+{
+ return -ENXIO;
+}
+#endif
+
+static int azx_get_crosststamp(struct snd_pcm_substream *substream,
+ struct system_device_crosststamp *xtstamp)
+{
+ return get_device_system_crosststamp(azx_get_sync_time,
+ substream, NULL, xtstamp);
+}
+
+static inline bool is_link_time_supported(struct snd_pcm_runtime *runtime,
+ struct snd_pcm_audio_tstamp_config *ts)
+{
+ if (runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME)
+ if (ts->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED)
+ return true;
+
+ return false;
+}
+
+static int azx_get_time_info(struct snd_pcm_substream *substream,
+ struct timespec64 *system_ts, struct timespec64 *audio_ts,
+ struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
+ struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
+{
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct system_device_crosststamp xtstamp;
+ int ret;
+ u64 nsec;
+
+ if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ATIME) &&
+ (audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK)) {
+
+ snd_pcm_gettime(substream->runtime, system_ts);
+
+ nsec = timecounter_read(&azx_dev->core.tc);
+ nsec = div_u64(nsec, 3); /* can be optimized */
+ if (audio_tstamp_config->report_delay)
+ nsec = azx_adjust_codec_delay(substream, nsec);
+
+ *audio_ts = ns_to_timespec64(nsec);
+
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
+ audio_tstamp_report->accuracy_report = 1; /* rest of structure is valid */
+ audio_tstamp_report->accuracy = 42; /* 24 MHz WallClock == 42ns resolution */
+
+ } else if (is_link_time_supported(runtime, audio_tstamp_config)) {
+
+ ret = azx_get_crosststamp(substream, &xtstamp);
+ if (ret)
+ return ret;
+
+ switch (runtime->tstamp_type) {
+ case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC:
+ return -EINVAL;
+
+ case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW:
+ *system_ts = ktime_to_timespec64(xtstamp.sys_monoraw);
+ break;
+
+ default:
+ *system_ts = ktime_to_timespec64(xtstamp.sys_realtime);
+ break;
+
+ }
+
+ *audio_ts = ktime_to_timespec64(xtstamp.device);
+
+ audio_tstamp_report->actual_type =
+ SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED;
+ audio_tstamp_report->accuracy_report = 1;
+ /* 24 MHz WallClock == 42ns resolution */
+ audio_tstamp_report->accuracy = 42;
+
+ } else {
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
+ }
+
+ return 0;
+}
+
+static const struct snd_pcm_hardware azx_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ /* No full-resume yet implemented */
+ /* SNDRV_PCM_INFO_RESUME |*/
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */
+ SNDRV_PCM_INFO_HAS_LINK_ATIME |
+ 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 = AZX_MAX_BUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = AZX_MAX_BUF_SIZE / 2,
+ .periods_min = 2,
+ .periods_max = AZX_MAX_FRAG,
+ .fifo_size = 0,
+};
+
+static int azx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+ int buff_step;
+
+ snd_hda_codec_pcm_get(apcm->info);
+ mutex_lock(&chip->open_mutex);
+ azx_dev = azx_assign_device(chip, substream);
+ trace_azx_pcm_open(chip, azx_dev);
+ if (azx_dev == NULL) {
+ err = -EBUSY;
+ goto unlock;
+ }
+ runtime->private_data = azx_dev;
+
+ runtime->hw = azx_pcm_hw;
+ if (chip->gts_present)
+ runtime->hw.info |= SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME;
+ runtime->hw.channels_min = hinfo->channels_min;
+ runtime->hw.channels_max = hinfo->channels_max;
+ runtime->hw.formats = hinfo->formats;
+ runtime->hw.rates = hinfo->rates;
+ snd_pcm_limit_hw_rates(runtime);
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+ /* avoid wrap-around with wall-clock */
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+ 20,
+ 178000000);
+
+ if (chip->align_buffer_size)
+ /* constrain buffer sizes to be multiple of 128
+ bytes. This is more efficient in terms of memory
+ access but isn't required by the HDA spec and
+ prevents users from specifying exact period/buffer
+ sizes. For example for 44.1kHz, a period size set
+ to 20ms will be rounded to 19.59ms. */
+ buff_step = 128;
+ else
+ /* Don't enforce steps on buffer sizes, still need to
+ be multiple of 4 bytes (HDA spec). Tested on Intel
+ HDA controllers, may not work on all devices where
+ option needs to be disabled */
+ buff_step = 4;
+
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ buff_step);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ buff_step);
+ snd_hda_power_up(apcm->codec);
+ if (hinfo->ops.open)
+ err = hinfo->ops.open(hinfo, apcm->codec, substream);
+ else
+ err = -ENODEV;
+ if (err < 0) {
+ azx_release_device(azx_dev);
+ goto powerdown;
+ }
+ snd_pcm_limit_hw_rates(runtime);
+ /* sanity check */
+ if (snd_BUG_ON(!runtime->hw.channels_min) ||
+ snd_BUG_ON(!runtime->hw.channels_max) ||
+ snd_BUG_ON(!runtime->hw.formats) ||
+ snd_BUG_ON(!runtime->hw.rates)) {
+ azx_release_device(azx_dev);
+ if (hinfo->ops.close)
+ hinfo->ops.close(hinfo, apcm->codec, substream);
+ err = -EINVAL;
+ goto powerdown;
+ }
+
+ /* disable LINK_ATIME timestamps for capture streams
+ until we figure out how to handle digital inputs */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK; /* legacy */
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_LINK_ATIME;
+ }
+
+ snd_pcm_set_sync(substream);
+ mutex_unlock(&chip->open_mutex);
+ return 0;
+
+ powerdown:
+ snd_hda_power_down(apcm->codec);
+ unlock:
+ mutex_unlock(&chip->open_mutex);
+ snd_hda_codec_pcm_put(apcm->info);
+ return err;
+}
+
+static int azx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *area)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ if (chip->ops->pcm_mmap_prepare)
+ chip->ops->pcm_mmap_prepare(substream, area);
+ return snd_pcm_lib_default_mmap(substream, area);
+}
+
+static const struct snd_pcm_ops azx_pcm_ops = {
+ .open = azx_pcm_open,
+ .close = azx_pcm_close,
+ .hw_params = azx_pcm_hw_params,
+ .hw_free = azx_pcm_hw_free,
+ .prepare = azx_pcm_prepare,
+ .trigger = azx_pcm_trigger,
+ .pointer = azx_pcm_pointer,
+ .get_time_info = azx_get_time_info,
+ .mmap = azx_pcm_mmap,
+};
+
+static void azx_pcm_free(struct snd_pcm *pcm)
+{
+ struct azx_pcm *apcm = pcm->private_data;
+ if (apcm) {
+ list_del(&apcm->list);
+ apcm->info->pcm = NULL;
+ kfree(apcm);
+ }
+}
+
+#define MAX_PREALLOC_SIZE (32 * 1024 * 1024)
+
+int snd_hda_attach_pcm_stream(struct hda_bus *_bus, struct hda_codec *codec,
+ struct hda_pcm *cpcm)
+{
+ struct hdac_bus *bus = &_bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct snd_pcm *pcm;
+ struct azx_pcm *apcm;
+ int pcm_dev = cpcm->device;
+ unsigned int size;
+ int s, err;
+ int type = SNDRV_DMA_TYPE_DEV_SG;
+
+ list_for_each_entry(apcm, &chip->pcm_list, list) {
+ if (apcm->pcm->device == pcm_dev) {
+ dev_err(chip->card->dev, "PCM %d already exists\n",
+ pcm_dev);
+ return -EBUSY;
+ }
+ }
+ err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
+ cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams,
+ cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams,
+ &pcm);
+ if (err < 0)
+ return err;
+ strlcpy(pcm->name, cpcm->name, sizeof(pcm->name));
+ apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+ if (apcm == NULL) {
+ snd_device_free(chip->card, pcm);
+ return -ENOMEM;
+ }
+ apcm->chip = chip;
+ apcm->pcm = pcm;
+ apcm->codec = codec;
+ apcm->info = cpcm;
+ pcm->private_data = apcm;
+ pcm->private_free = azx_pcm_free;
+ if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM)
+ pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+ list_add_tail(&apcm->list, &chip->pcm_list);
+ cpcm->pcm = pcm;
+ for (s = 0; s < 2; s++) {
+ if (cpcm->stream[s].substreams)
+ snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
+ }
+ /* buffer pre-allocation */
+ size = CONFIG_SND_HDA_PREALLOC_SIZE * 1024;
+ if (size > MAX_PREALLOC_SIZE)
+ size = MAX_PREALLOC_SIZE;
+ if (chip->uc_buffer)
+ type = SNDRV_DMA_TYPE_DEV_UC_SG;
+ snd_pcm_set_managed_buffer_all(pcm, type, chip->card->dev,
+ size, MAX_PREALLOC_SIZE);
+ return 0;
+}
+
+static unsigned int azx_command_addr(u32 cmd)
+{
+ unsigned int addr = cmd >> 28;
+
+ if (addr >= AZX_MAX_CODECS) {
+ snd_BUG();
+ addr = 0;
+ }
+
+ return addr;
+}
+
+/* receive a response */
+static int azx_rirb_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ struct azx *chip = bus_to_azx(bus);
+ struct hda_bus *hbus = &chip->bus;
+ int err;
+
+ again:
+ err = snd_hdac_bus_get_response(bus, addr, res);
+ if (!err)
+ return 0;
+
+ if (hbus->no_response_fallback)
+ return -EIO;
+
+ if (!bus->polling_mode) {
+ dev_warn(chip->card->dev,
+ "azx_get_response timeout, switching to polling mode: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ bus->polling_mode = 1;
+ goto again;
+ }
+
+ if (chip->msi) {
+ dev_warn(chip->card->dev,
+ "No response from codec, disabling MSI: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ if (chip->ops->disable_msi_reset_irq &&
+ chip->ops->disable_msi_reset_irq(chip) < 0)
+ return -EIO;
+ goto again;
+ }
+
+ if (chip->probing) {
+ /* If this critical timeout happens during the codec probing
+ * phase, this is likely an access to a non-existing codec
+ * slot. Better to return an error and reset the system.
+ */
+ return -EIO;
+ }
+
+ /* no fallback mechanism? */
+ if (!chip->fallback_to_single_cmd)
+ return -EIO;
+
+ /* a fatal communication error; need either to reset or to fallback
+ * to the single_cmd mode
+ */
+ if (hbus->allow_bus_reset && !hbus->response_reset && !hbus->in_reset) {
+ hbus->response_reset = 1;
+ dev_err(chip->card->dev,
+ "No response from codec, resetting bus: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ return -EAGAIN; /* give a chance to retry */
+ }
+
+ dev_err(chip->card->dev,
+ "azx_get_response timeout, switching to single_cmd mode: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ chip->single_cmd = 1;
+ hbus->response_reset = 0;
+ snd_hdac_bus_stop_cmd_io(bus);
+ return -EIO;
+}
+
+/*
+ * Use the single immediate command instead of CORB/RIRB for simplicity
+ *
+ * Note: according to Intel, this is not preferred use. The command was
+ * intended for the BIOS only, and may get confused with unsolicited
+ * responses. So, we shouldn't use it for normal operation from the
+ * driver.
+ * I left the codes, however, for debugging/testing purposes.
+ */
+
+/* receive a response */
+static int azx_single_wait_for_response(struct azx *chip, unsigned int addr)
+{
+ int timeout = 50;
+
+ while (timeout--) {
+ /* check IRV busy bit */
+ if (azx_readw(chip, IRS) & AZX_IRS_VALID) {
+ /* reuse rirb.res as the response return value */
+ azx_bus(chip)->rirb.res[addr] = azx_readl(chip, IR);
+ return 0;
+ }
+ udelay(1);
+ }
+ if (printk_ratelimit())
+ dev_dbg(chip->card->dev, "get_response timeout: IRS=0x%x\n",
+ azx_readw(chip, IRS));
+ azx_bus(chip)->rirb.res[addr] = -1;
+ return -EIO;
+}
+
+/* send a command */
+static int azx_single_send_cmd(struct hdac_bus *bus, u32 val)
+{
+ struct azx *chip = bus_to_azx(bus);
+ unsigned int addr = azx_command_addr(val);
+ int timeout = 50;
+
+ bus->last_cmd[azx_command_addr(val)] = val;
+ while (timeout--) {
+ /* check ICB busy bit */
+ if (!((azx_readw(chip, IRS) & AZX_IRS_BUSY))) {
+ /* Clear IRV valid bit */
+ azx_writew(chip, IRS, azx_readw(chip, IRS) |
+ AZX_IRS_VALID);
+ azx_writel(chip, IC, val);
+ azx_writew(chip, IRS, azx_readw(chip, IRS) |
+ AZX_IRS_BUSY);
+ return azx_single_wait_for_response(chip, addr);
+ }
+ udelay(1);
+ }
+ if (printk_ratelimit())
+ dev_dbg(chip->card->dev,
+ "send_cmd timeout: IRS=0x%x, val=0x%x\n",
+ azx_readw(chip, IRS), val);
+ return -EIO;
+}
+
+/* receive a response */
+static int azx_single_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ if (res)
+ *res = bus->rirb.res[addr];
+ return 0;
+}
+
+/*
+ * The below are the main callbacks from hda_codec.
+ *
+ * They are just the skeleton to call sub-callbacks according to the
+ * current setting of chip->single_cmd.
+ */
+
+/* send a command */
+static int azx_send_cmd(struct hdac_bus *bus, unsigned int val)
+{
+ struct azx *chip = bus_to_azx(bus);
+
+ if (chip->disabled)
+ return 0;
+ if (chip->single_cmd)
+ return azx_single_send_cmd(bus, val);
+ else
+ return snd_hdac_bus_send_cmd(bus, val);
+}
+
+/* get a response */
+static int azx_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ struct azx *chip = bus_to_azx(bus);
+
+ if (chip->disabled)
+ return 0;
+ if (chip->single_cmd)
+ return azx_single_get_response(bus, addr, res);
+ else
+ return azx_rirb_get_response(bus, addr, res);
+}
+
+static const struct hdac_bus_ops bus_core_ops = {
+ .command = azx_send_cmd,
+ .get_response = azx_get_response,
+};
+
+#ifdef CONFIG_SND_HDA_DSP_LOADER
+/*
+ * DSP loading code (e.g. for CA0132)
+ */
+
+/* use the first stream for loading DSP */
+static struct azx_dev *
+azx_get_dsp_loader_dev(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+
+ list_for_each_entry(s, &bus->stream_list, list)
+ if (s->index == chip->playback_index_offset)
+ return stream_to_azx_dev(s);
+
+ return NULL;
+}
+
+int snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format,
+ unsigned int byte_size,
+ struct snd_dma_buffer *bufp)
+{
+ struct hdac_bus *bus = &codec->bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev;
+ struct hdac_stream *hstr;
+ bool saved = false;
+ int err;
+
+ azx_dev = azx_get_dsp_loader_dev(chip);
+ hstr = azx_stream(azx_dev);
+ spin_lock_irq(&bus->reg_lock);
+ if (hstr->opened) {
+ chip->saved_azx_dev = *azx_dev;
+ saved = true;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+
+ err = snd_hdac_dsp_prepare(hstr, format, byte_size, bufp);
+ if (err < 0) {
+ spin_lock_irq(&bus->reg_lock);
+ if (saved)
+ *azx_dev = chip->saved_azx_dev;
+ spin_unlock_irq(&bus->reg_lock);
+ return err;
+ }
+
+ hstr->prepared = 0;
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_prepare);
+
+void snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start)
+{
+ struct hdac_bus *bus = &codec->bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev = azx_get_dsp_loader_dev(chip);
+
+ snd_hdac_dsp_trigger(azx_stream(azx_dev), start);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_trigger);
+
+void snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec,
+ struct snd_dma_buffer *dmab)
+{
+ struct hdac_bus *bus = &codec->bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev = azx_get_dsp_loader_dev(chip);
+ struct hdac_stream *hstr = azx_stream(azx_dev);
+
+ if (!dmab->area || !hstr->locked)
+ return;
+
+ snd_hdac_dsp_cleanup(hstr, dmab);
+ spin_lock_irq(&bus->reg_lock);
+ if (hstr->opened)
+ *azx_dev = chip->saved_azx_dev;
+ hstr->locked = false;
+ spin_unlock_irq(&bus->reg_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_cleanup);
+#endif /* CONFIG_SND_HDA_DSP_LOADER */
+
+/*
+ * reset and start the controller registers
+ */
+void azx_init_chip(struct azx *chip, bool full_reset)
+{
+ if (snd_hdac_bus_init_chip(azx_bus(chip), full_reset)) {
+ /* correct RINTCNT for CXT */
+ if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND)
+ azx_writew(chip, RINTCNT, 0xc0);
+ }
+}
+EXPORT_SYMBOL_GPL(azx_init_chip);
+
+void azx_stop_all_streams(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+
+ snd_hdac_stop_streams(bus);
+}
+EXPORT_SYMBOL_GPL(azx_stop_all_streams);
+
+void azx_stop_chip(struct azx *chip)
+{
+ snd_hdac_bus_stop_chip(azx_bus(chip));
+}
+EXPORT_SYMBOL_GPL(azx_stop_chip);
+
+/*
+ * interrupt handler
+ */
+static void stream_update(struct hdac_bus *bus, struct hdac_stream *s)
+{
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev = stream_to_azx_dev(s);
+
+ /* check whether this IRQ is really acceptable */
+ if (!chip->ops->position_check ||
+ chip->ops->position_check(chip, azx_dev)) {
+ spin_unlock(&bus->reg_lock);
+ snd_pcm_period_elapsed(azx_stream(azx_dev)->substream);
+ spin_lock(&bus->reg_lock);
+ }
+}
+
+irqreturn_t azx_interrupt(int irq, void *dev_id)
+{
+ struct azx *chip = dev_id;
+ struct hdac_bus *bus = azx_bus(chip);
+ u32 status;
+ bool active, handled = false;
+ int repeat = 0; /* count for avoiding endless loop */
+
+#ifdef CONFIG_PM
+ if (azx_has_pm_runtime(chip))
+ if (!pm_runtime_active(chip->card->dev))
+ return IRQ_NONE;
+#endif
+
+ spin_lock(&bus->reg_lock);
+
+ if (chip->disabled)
+ goto unlock;
+
+ do {
+ status = azx_readl(chip, INTSTS);
+ if (status == 0 || status == 0xffffffff)
+ break;
+
+ handled = true;
+ active = false;
+ if (snd_hdac_bus_handle_stream_irq(bus, status, stream_update))
+ active = true;
+
+ status = azx_readb(chip, RIRBSTS);
+ if (status & RIRB_INT_MASK) {
+ /*
+ * Clearing the interrupt status here ensures that no
+ * interrupt gets masked after the RIRB wp is read in
+ * snd_hdac_bus_update_rirb. This avoids a possible
+ * race condition where codec response in RIRB may
+ * remain unserviced by IRQ, eventually falling back
+ * to polling mode in azx_rirb_get_response.
+ */
+ azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+ active = true;
+ if (status & RIRB_INT_RESPONSE) {
+ if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND)
+ udelay(80);
+ snd_hdac_bus_update_rirb(bus);
+ }
+ }
+ } while (active && ++repeat < 10);
+
+ unlock:
+ spin_unlock(&bus->reg_lock);
+
+ return IRQ_RETVAL(handled);
+}
+EXPORT_SYMBOL_GPL(azx_interrupt);
+
+/*
+ * Codec initerface
+ */
+
+/*
+ * Probe the given codec address
+ */
+static int probe_codec(struct azx *chip, int addr)
+{
+ unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+ (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+ struct hdac_bus *bus = azx_bus(chip);
+ int err;
+ unsigned int res = -1;
+
+ mutex_lock(&bus->cmd_mutex);
+ chip->probing = 1;
+ azx_send_cmd(bus, cmd);
+ err = azx_get_response(bus, addr, &res);
+ chip->probing = 0;
+ mutex_unlock(&bus->cmd_mutex);
+ if (err < 0 || res == -1)
+ return -EIO;
+ dev_dbg(chip->card->dev, "codec #%d probed OK\n", addr);
+ return 0;
+}
+
+void snd_hda_bus_reset(struct hda_bus *bus)
+{
+ struct azx *chip = bus_to_azx(&bus->core);
+
+ bus->in_reset = 1;
+ azx_stop_chip(chip);
+ azx_init_chip(chip, true);
+ if (bus->core.chip_init)
+ snd_hda_bus_reset_codecs(bus);
+ bus->in_reset = 0;
+}
+
+/* HD-audio bus initialization */
+int azx_bus_init(struct azx *chip, const char *model)
+{
+ struct hda_bus *bus = &chip->bus;
+ int err;
+
+ err = snd_hdac_bus_init(&bus->core, chip->card->dev, &bus_core_ops);
+ if (err < 0)
+ return err;
+
+ bus->card = chip->card;
+ mutex_init(&bus->prepare_mutex);
+ bus->pci = chip->pci;
+ bus->modelname = model;
+ bus->mixer_assigned = -1;
+ bus->core.snoop = azx_snoop(chip);
+ if (chip->get_position[0] != azx_get_pos_lpib ||
+ chip->get_position[1] != azx_get_pos_lpib)
+ bus->core.use_posbuf = true;
+ bus->core.bdl_pos_adj = chip->bdl_pos_adj;
+ if (chip->driver_caps & AZX_DCAPS_CORBRP_SELF_CLEAR)
+ bus->core.corbrp_self_clear = true;
+
+ if (chip->driver_caps & AZX_DCAPS_4K_BDLE_BOUNDARY)
+ bus->core.align_bdle_4k = true;
+
+ /* enable sync_write flag for stable communication as default */
+ bus->core.sync_write = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(azx_bus_init);
+
+/* Probe codecs */
+int azx_probe_codecs(struct azx *chip, unsigned int max_slots)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ int c, codecs, err;
+
+ codecs = 0;
+ if (!max_slots)
+ max_slots = AZX_DEFAULT_CODECS;
+
+ /* First try to probe all given codec slots */
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+ if (probe_codec(chip, c) < 0) {
+ /* Some BIOSen give you wrong codec addresses
+ * that don't exist
+ */
+ dev_warn(chip->card->dev,
+ "Codec #%d probe error; disabling it...\n", c);
+ bus->codec_mask &= ~(1 << c);
+ /* More badly, accessing to a non-existing
+ * codec often screws up the controller chip,
+ * and disturbs the further communications.
+ * Thus if an error occurs during probing,
+ * better to reset the controller chip to
+ * get back to the sanity state.
+ */
+ azx_stop_chip(chip);
+ azx_init_chip(chip, true);
+ }
+ }
+ }
+
+ /* Then create codec instances */
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+ struct hda_codec *codec;
+ err = snd_hda_codec_new(&chip->bus, chip->card, c, &codec);
+ if (err < 0)
+ continue;
+ codec->jackpoll_interval = chip->jackpoll_interval;
+ codec->beep_mode = chip->beep_mode;
+ codecs++;
+ }
+ }
+ if (!codecs) {
+ dev_err(chip->card->dev, "no codecs initialized\n");
+ return -ENXIO;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(azx_probe_codecs);
+
+/* configure each codec instance */
+int azx_codec_configure(struct azx *chip)
+{
+ struct hda_codec *codec, *next;
+ int success = 0;
+
+ list_for_each_codec(codec, &chip->bus) {
+ if (!snd_hda_codec_configure(codec))
+ success++;
+ }
+
+ if (success) {
+ /* unregister failed codecs if any codec has been probed */
+ list_for_each_codec_safe(codec, next, &chip->bus) {
+ if (!codec->configured) {
+ codec_err(codec, "Unable to configure, disabling\n");
+ snd_hdac_device_unregister(&codec->core);
+ }
+ }
+ }
+
+ return success ? 0 : -ENODEV;
+}
+EXPORT_SYMBOL_GPL(azx_codec_configure);
+
+static int stream_direction(struct azx *chip, unsigned char index)
+{
+ if (index >= chip->capture_index_offset &&
+ index < chip->capture_index_offset + chip->capture_streams)
+ return SNDRV_PCM_STREAM_CAPTURE;
+ return SNDRV_PCM_STREAM_PLAYBACK;
+}
+
+/* initialize SD streams */
+int azx_init_streams(struct azx *chip)
+{
+ int i;
+ int stream_tags[2] = { 0, 0 };
+
+ /* initialize each stream (aka device)
+ * assign the starting bdl address to each stream (device)
+ * and initialize
+ */
+ for (i = 0; i < chip->num_streams; i++) {
+ struct azx_dev *azx_dev = kzalloc(sizeof(*azx_dev), GFP_KERNEL);
+ int dir, tag;
+
+ if (!azx_dev)
+ return -ENOMEM;
+
+ dir = stream_direction(chip, i);
+ /* stream tag must be unique throughout
+ * the stream direction group,
+ * valid values 1...15
+ * use separate stream tag if the flag
+ * AZX_DCAPS_SEPARATE_STREAM_TAG is used
+ */
+ if (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG)
+ tag = ++stream_tags[dir];
+ else
+ tag = i + 1;
+ snd_hdac_stream_init(azx_bus(chip), azx_stream(azx_dev),
+ i, dir, tag);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(azx_init_streams);
+
+void azx_free_streams(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+
+ while (!list_empty(&bus->stream_list)) {
+ s = list_first_entry(&bus->stream_list, struct hdac_stream, list);
+ list_del(&s->list);
+ kfree(stream_to_azx_dev(s));
+ }
+}
+EXPORT_SYMBOL_GPL(azx_free_streams);
diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h
new file mode 100644
index 000000000..324cba13c
--- /dev/null
+++ b/sound/pci/hda/hda_controller.h
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Common functionality for the alsa driver code base for HD Audio.
+ */
+
+#ifndef __SOUND_HDA_CONTROLLER_H
+#define __SOUND_HDA_CONTROLLER_H
+
+#include <linux/timecounter.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/hda_codec.h>
+#include <sound/hda_register.h>
+
+#define AZX_MAX_CODECS HDA_MAX_CODECS
+#define AZX_DEFAULT_CODECS 4
+
+/* driver quirks (capabilities) */
+/* bits 0-7 are used for indicating driver type */
+#define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */
+#define AZX_DCAPS_NO_MSI (1 << 9) /* No MSI support */
+#define AZX_DCAPS_SNOOP_MASK (3 << 10) /* snoop type mask */
+#define AZX_DCAPS_SNOOP_OFF (1 << 12) /* snoop default off */
+#ifdef CONFIG_SND_HDA_I915
+#define AZX_DCAPS_I915_COMPONENT (1 << 13) /* bind with i915 gfx */
+#else
+#define AZX_DCAPS_I915_COMPONENT 0 /* NOP */
+#endif
+/* 14 unused */
+#define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */
+#define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */
+#define AZX_DCAPS_AMD_WORKAROUND (1 << 17) /* AMD-specific workaround */
+#define AZX_DCAPS_NO_64BIT (1 << 18) /* No 64bit address */
+/* 19 unused */
+#define AZX_DCAPS_OLD_SSYNC (1 << 20) /* Old SSYNC reg for ICH */
+#define AZX_DCAPS_NO_ALIGN_BUFSIZE (1 << 21) /* no buffer size alignment */
+/* 22 unused */
+#define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */
+/* 24 unused */
+#define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */
+#define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */
+#define AZX_DCAPS_RETRY_PROBE (1 << 27) /* retry probe if no codec is configured */
+#define AZX_DCAPS_CORBRP_SELF_CLEAR (1 << 28) /* CORBRP clears itself after reset */
+#define AZX_DCAPS_NO_MSI64 (1 << 29) /* Stick to 32-bit MSIs */
+#define AZX_DCAPS_SEPARATE_STREAM_TAG (1 << 30) /* capture and playback use separate stream tag */
+
+enum {
+ AZX_SNOOP_TYPE_NONE,
+ AZX_SNOOP_TYPE_SCH,
+ AZX_SNOOP_TYPE_ATI,
+ AZX_SNOOP_TYPE_NVIDIA,
+};
+
+struct azx_dev {
+ struct hdac_stream core;
+
+ unsigned int irq_pending:1;
+ /*
+ * For VIA:
+ * A flag to ensure DMA position is 0
+ * when link position is not greater than FIFO size
+ */
+ unsigned int insufficient:1;
+};
+
+#define azx_stream(dev) (&(dev)->core)
+#define stream_to_azx_dev(s) container_of(s, struct azx_dev, core)
+
+struct azx;
+
+/* Functions to read/write to hda registers. */
+struct hda_controller_ops {
+ /* Disable msi if supported, PCI only */
+ int (*disable_msi_reset_irq)(struct azx *);
+ void (*pcm_mmap_prepare)(struct snd_pcm_substream *substream,
+ struct vm_area_struct *area);
+ /* Check if current position is acceptable */
+ int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
+ /* enable/disable the link power */
+ int (*link_power)(struct azx *chip, bool enable);
+};
+
+struct azx_pcm {
+ struct azx *chip;
+ struct snd_pcm *pcm;
+ struct hda_codec *codec;
+ struct hda_pcm *info;
+ struct list_head list;
+};
+
+typedef unsigned int (*azx_get_pos_callback_t)(struct azx *, struct azx_dev *);
+typedef int (*azx_get_delay_callback_t)(struct azx *, struct azx_dev *, unsigned int pos);
+
+struct azx {
+ struct hda_bus bus;
+
+ struct snd_card *card;
+ struct pci_dev *pci;
+ int dev_index;
+
+ /* chip type specific */
+ int driver_type;
+ unsigned int driver_caps;
+ int playback_streams;
+ int playback_index_offset;
+ int capture_streams;
+ int capture_index_offset;
+ int num_streams;
+ int jackpoll_interval; /* jack poll interval in jiffies */
+
+ /* Register interaction. */
+ const struct hda_controller_ops *ops;
+
+ /* position adjustment callbacks */
+ azx_get_pos_callback_t get_position[2];
+ azx_get_delay_callback_t get_delay[2];
+
+ /* locks */
+ struct mutex open_mutex; /* Prevents concurrent open/close operations */
+
+ /* PCM */
+ struct list_head pcm_list; /* azx_pcm list */
+
+ /* HD codec */
+ int codec_probe_mask; /* copied from probe_mask option */
+ unsigned int beep_mode;
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ const struct firmware *fw;
+#endif
+
+ /* flags */
+ int bdl_pos_adj;
+ unsigned int running:1;
+ unsigned int fallback_to_single_cmd:1;
+ unsigned int single_cmd:1;
+ unsigned int msi:1;
+ unsigned int probing:1; /* codec probing phase */
+ unsigned int snoop:1;
+ unsigned int uc_buffer:1; /* non-cached pages for stream buffers */
+ unsigned int align_buffer_size:1;
+ unsigned int region_requested:1;
+ unsigned int disabled:1; /* disabled by vga_switcheroo */
+ unsigned int pm_prepared:1;
+
+ /* GTS present */
+ unsigned int gts_present:1;
+
+#ifdef CONFIG_SND_HDA_DSP_LOADER
+ struct azx_dev saved_azx_dev;
+#endif
+};
+
+#define azx_bus(chip) (&(chip)->bus.core)
+#define bus_to_azx(_bus) container_of(_bus, struct azx, bus.core)
+
+static inline bool azx_snoop(struct azx *chip)
+{
+ return !IS_ENABLED(CONFIG_X86) || chip->snoop;
+}
+
+/*
+ * macros for easy use
+ */
+
+#define azx_writel(chip, reg, value) \
+ snd_hdac_chip_writel(azx_bus(chip), reg, value)
+#define azx_readl(chip, reg) \
+ snd_hdac_chip_readl(azx_bus(chip), reg)
+#define azx_writew(chip, reg, value) \
+ snd_hdac_chip_writew(azx_bus(chip), reg, value)
+#define azx_readw(chip, reg) \
+ snd_hdac_chip_readw(azx_bus(chip), reg)
+#define azx_writeb(chip, reg, value) \
+ snd_hdac_chip_writeb(azx_bus(chip), reg, value)
+#define azx_readb(chip, reg) \
+ snd_hdac_chip_readb(azx_bus(chip), reg)
+
+#define azx_has_pm_runtime(chip) \
+ ((chip)->driver_caps & AZX_DCAPS_PM_RUNTIME)
+
+/* PCM setup */
+static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream)
+{
+ return substream->runtime->private_data;
+}
+unsigned int azx_get_position(struct azx *chip, struct azx_dev *azx_dev);
+unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev);
+unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev);
+
+/* Stream control. */
+void azx_stop_all_streams(struct azx *chip);
+
+/* Allocation functions. */
+#define azx_alloc_stream_pages(chip) \
+ snd_hdac_bus_alloc_stream_pages(azx_bus(chip))
+#define azx_free_stream_pages(chip) \
+ snd_hdac_bus_free_stream_pages(azx_bus(chip))
+
+/* Low level azx interface */
+void azx_init_chip(struct azx *chip, bool full_reset);
+void azx_stop_chip(struct azx *chip);
+#define azx_enter_link_reset(chip) \
+ snd_hdac_bus_enter_link_reset(azx_bus(chip))
+irqreturn_t azx_interrupt(int irq, void *dev_id);
+
+/* Codec interface */
+int azx_bus_init(struct azx *chip, const char *model);
+int azx_probe_codecs(struct azx *chip, unsigned int max_slots);
+int azx_codec_configure(struct azx *chip);
+int azx_init_streams(struct azx *chip);
+void azx_free_streams(struct azx *chip);
+
+#endif /* __SOUND_HDA_CONTROLLER_H */
diff --git a/sound/pci/hda/hda_controller_trace.h b/sound/pci/hda/hda_controller_trace.h
new file mode 100644
index 000000000..bf48304e2
--- /dev/null
+++ b/sound/pci/hda/hda_controller_trace.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM hda_controller
+#define TRACE_INCLUDE_FILE hda_controller_trace
+
+#if !defined(_TRACE_HDA_CONTROLLER_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_HDA_CONTROLLER_H
+
+#include <linux/tracepoint.h>
+
+struct azx;
+struct azx_dev;
+
+TRACE_EVENT(azx_pcm_trigger,
+
+ TP_PROTO(struct azx *chip, struct azx_dev *dev, int cmd),
+
+ TP_ARGS(chip, dev, cmd),
+
+ TP_STRUCT__entry(
+ __field( int, card )
+ __field( int, idx )
+ __field( int, cmd )
+ ),
+
+ TP_fast_assign(
+ __entry->card = (chip)->card->number;
+ __entry->idx = (dev)->core.index;
+ __entry->cmd = cmd;
+ ),
+
+ TP_printk("[%d:%d] cmd=%d", __entry->card, __entry->idx, __entry->cmd)
+);
+
+TRACE_EVENT(azx_get_position,
+
+ TP_PROTO(struct azx *chip, struct azx_dev *dev, unsigned int pos, unsigned int delay),
+
+ TP_ARGS(chip, dev, pos, delay),
+
+ TP_STRUCT__entry(
+ __field( int, card )
+ __field( int, idx )
+ __field( unsigned int, pos )
+ __field( unsigned int, delay )
+ ),
+
+ TP_fast_assign(
+ __entry->card = (chip)->card->number;
+ __entry->idx = (dev)->core.index;
+ __entry->pos = pos;
+ __entry->delay = delay;
+ ),
+
+ TP_printk("[%d:%d] pos=%u, delay=%u", __entry->card, __entry->idx, __entry->pos, __entry->delay)
+);
+
+DECLARE_EVENT_CLASS(azx_pcm,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+
+ TP_ARGS(chip, azx_dev),
+
+ TP_STRUCT__entry(
+ __field( unsigned char, stream_tag )
+ ),
+
+ TP_fast_assign(
+ __entry->stream_tag = (azx_dev)->core.stream_tag;
+ ),
+
+ TP_printk("stream_tag: %d", __entry->stream_tag)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_open,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_close,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_hw_params,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_prepare,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+#endif /* _TRACE_HDA_CONTROLLER_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>
diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c
new file mode 100644
index 000000000..136477ed4
--- /dev/null
+++ b/sound/pci/hda/hda_eld.c
@@ -0,0 +1,767 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic routines and proc interface for ELD(EDID Like Data) information
+ *
+ * Copyright(c) 2008 Intel Corporation.
+ * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ * Authors:
+ * Wu Fengguang <wfg@linux.intel.com>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <asm/unaligned.h>
+#include <sound/hda_chmap.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+
+enum eld_versions {
+ ELD_VER_CEA_861D = 2,
+ ELD_VER_PARTIAL = 31,
+};
+
+enum cea_edid_versions {
+ CEA_EDID_VER_NONE = 0,
+ CEA_EDID_VER_CEA861 = 1,
+ CEA_EDID_VER_CEA861A = 2,
+ CEA_EDID_VER_CEA861BCD = 3,
+ CEA_EDID_VER_RESERVED = 4,
+};
+
+static const char * const eld_connection_type_names[4] = {
+ "HDMI",
+ "DisplayPort",
+ "2-reserved",
+ "3-reserved"
+};
+
+enum cea_audio_coding_types {
+ AUDIO_CODING_TYPE_REF_STREAM_HEADER = 0,
+ AUDIO_CODING_TYPE_LPCM = 1,
+ AUDIO_CODING_TYPE_AC3 = 2,
+ AUDIO_CODING_TYPE_MPEG1 = 3,
+ AUDIO_CODING_TYPE_MP3 = 4,
+ AUDIO_CODING_TYPE_MPEG2 = 5,
+ AUDIO_CODING_TYPE_AACLC = 6,
+ AUDIO_CODING_TYPE_DTS = 7,
+ AUDIO_CODING_TYPE_ATRAC = 8,
+ AUDIO_CODING_TYPE_SACD = 9,
+ AUDIO_CODING_TYPE_EAC3 = 10,
+ AUDIO_CODING_TYPE_DTS_HD = 11,
+ AUDIO_CODING_TYPE_MLP = 12,
+ AUDIO_CODING_TYPE_DST = 13,
+ AUDIO_CODING_TYPE_WMAPRO = 14,
+ AUDIO_CODING_TYPE_REF_CXT = 15,
+ /* also include valid xtypes below */
+ AUDIO_CODING_TYPE_HE_AAC = 15,
+ AUDIO_CODING_TYPE_HE_AAC2 = 16,
+ AUDIO_CODING_TYPE_MPEG_SURROUND = 17,
+};
+
+enum cea_audio_coding_xtypes {
+ AUDIO_CODING_XTYPE_HE_REF_CT = 0,
+ AUDIO_CODING_XTYPE_HE_AAC = 1,
+ AUDIO_CODING_XTYPE_HE_AAC2 = 2,
+ AUDIO_CODING_XTYPE_MPEG_SURROUND = 3,
+ AUDIO_CODING_XTYPE_FIRST_RESERVED = 4,
+};
+
+static const char * const cea_audio_coding_type_names[] = {
+ /* 0 */ "undefined",
+ /* 1 */ "LPCM",
+ /* 2 */ "AC-3",
+ /* 3 */ "MPEG1",
+ /* 4 */ "MP3",
+ /* 5 */ "MPEG2",
+ /* 6 */ "AAC-LC",
+ /* 7 */ "DTS",
+ /* 8 */ "ATRAC",
+ /* 9 */ "DSD (One Bit Audio)",
+ /* 10 */ "E-AC-3/DD+ (Dolby Digital Plus)",
+ /* 11 */ "DTS-HD",
+ /* 12 */ "MLP (Dolby TrueHD)",
+ /* 13 */ "DST",
+ /* 14 */ "WMAPro",
+ /* 15 */ "HE-AAC",
+ /* 16 */ "HE-AACv2",
+ /* 17 */ "MPEG Surround",
+};
+
+/*
+ * The following two lists are shared between
+ * - HDMI audio InfoFrame (source to sink)
+ * - CEA E-EDID Extension (sink to source)
+ */
+
+/*
+ * SS1:SS0 index => sample size
+ */
+static const int cea_sample_sizes[4] = {
+ 0, /* 0: Refer to Stream Header */
+ AC_SUPPCM_BITS_16, /* 1: 16 bits */
+ AC_SUPPCM_BITS_20, /* 2: 20 bits */
+ AC_SUPPCM_BITS_24, /* 3: 24 bits */
+};
+
+/*
+ * SF2:SF1:SF0 index => sampling frequency
+ */
+static const int cea_sampling_frequencies[8] = {
+ 0, /* 0: Refer to Stream Header */
+ SNDRV_PCM_RATE_32000, /* 1: 32000Hz */
+ SNDRV_PCM_RATE_44100, /* 2: 44100Hz */
+ SNDRV_PCM_RATE_48000, /* 3: 48000Hz */
+ SNDRV_PCM_RATE_88200, /* 4: 88200Hz */
+ SNDRV_PCM_RATE_96000, /* 5: 96000Hz */
+ SNDRV_PCM_RATE_176400, /* 6: 176400Hz */
+ SNDRV_PCM_RATE_192000, /* 7: 192000Hz */
+};
+
+static unsigned int hdmi_get_eld_data(struct hda_codec *codec, hda_nid_t nid,
+ int byte_index)
+{
+ unsigned int val;
+
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_HDMI_ELDD, byte_index);
+#ifdef BE_PARANOID
+ codec_info(codec, "HDMI: ELD data byte %d: 0x%x\n", byte_index, val);
+#endif
+ return val;
+}
+
+#define GRAB_BITS(buf, byte, lowbit, bits) \
+({ \
+ BUILD_BUG_ON(lowbit > 7); \
+ BUILD_BUG_ON(bits > 8); \
+ BUILD_BUG_ON(bits <= 0); \
+ \
+ (buf[byte] >> (lowbit)) & ((1 << (bits)) - 1); \
+})
+
+static void hdmi_update_short_audio_desc(struct hda_codec *codec,
+ struct cea_sad *a,
+ const unsigned char *buf)
+{
+ int i;
+ int val;
+
+ val = GRAB_BITS(buf, 1, 0, 7);
+ a->rates = 0;
+ for (i = 0; i < 7; i++)
+ if (val & (1 << i))
+ a->rates |= cea_sampling_frequencies[i + 1];
+
+ a->channels = GRAB_BITS(buf, 0, 0, 3);
+ a->channels++;
+
+ a->sample_bits = 0;
+ a->max_bitrate = 0;
+
+ a->format = GRAB_BITS(buf, 0, 3, 4);
+ switch (a->format) {
+ case AUDIO_CODING_TYPE_REF_STREAM_HEADER:
+ codec_info(codec, "HDMI: audio coding type 0 not expected\n");
+ break;
+
+ case AUDIO_CODING_TYPE_LPCM:
+ val = GRAB_BITS(buf, 2, 0, 3);
+ for (i = 0; i < 3; i++)
+ if (val & (1 << i))
+ a->sample_bits |= cea_sample_sizes[i + 1];
+ break;
+
+ case AUDIO_CODING_TYPE_AC3:
+ case AUDIO_CODING_TYPE_MPEG1:
+ case AUDIO_CODING_TYPE_MP3:
+ case AUDIO_CODING_TYPE_MPEG2:
+ case AUDIO_CODING_TYPE_AACLC:
+ case AUDIO_CODING_TYPE_DTS:
+ case AUDIO_CODING_TYPE_ATRAC:
+ a->max_bitrate = GRAB_BITS(buf, 2, 0, 8);
+ a->max_bitrate *= 8000;
+ break;
+
+ case AUDIO_CODING_TYPE_SACD:
+ break;
+
+ case AUDIO_CODING_TYPE_EAC3:
+ break;
+
+ case AUDIO_CODING_TYPE_DTS_HD:
+ break;
+
+ case AUDIO_CODING_TYPE_MLP:
+ break;
+
+ case AUDIO_CODING_TYPE_DST:
+ break;
+
+ case AUDIO_CODING_TYPE_WMAPRO:
+ a->profile = GRAB_BITS(buf, 2, 0, 3);
+ break;
+
+ case AUDIO_CODING_TYPE_REF_CXT:
+ a->format = GRAB_BITS(buf, 2, 3, 5);
+ if (a->format == AUDIO_CODING_XTYPE_HE_REF_CT ||
+ a->format >= AUDIO_CODING_XTYPE_FIRST_RESERVED) {
+ codec_info(codec,
+ "HDMI: audio coding xtype %d not expected\n",
+ a->format);
+ a->format = 0;
+ } else
+ a->format += AUDIO_CODING_TYPE_HE_AAC -
+ AUDIO_CODING_XTYPE_HE_AAC;
+ break;
+ }
+}
+
+/*
+ * Be careful, ELD buf could be totally rubbish!
+ */
+int snd_hdmi_parse_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e,
+ const unsigned char *buf, int size)
+{
+ int mnl;
+ int i;
+
+ memset(e, 0, sizeof(*e));
+ e->eld_ver = GRAB_BITS(buf, 0, 3, 5);
+ if (e->eld_ver != ELD_VER_CEA_861D &&
+ e->eld_ver != ELD_VER_PARTIAL) {
+ codec_info(codec, "HDMI: Unknown ELD version %d\n", e->eld_ver);
+ goto out_fail;
+ }
+
+ e->baseline_len = GRAB_BITS(buf, 2, 0, 8);
+ mnl = GRAB_BITS(buf, 4, 0, 5);
+ e->cea_edid_ver = GRAB_BITS(buf, 4, 5, 3);
+
+ e->support_hdcp = GRAB_BITS(buf, 5, 0, 1);
+ e->support_ai = GRAB_BITS(buf, 5, 1, 1);
+ e->conn_type = GRAB_BITS(buf, 5, 2, 2);
+ e->sad_count = GRAB_BITS(buf, 5, 4, 4);
+
+ e->aud_synch_delay = GRAB_BITS(buf, 6, 0, 8) * 2;
+ e->spk_alloc = GRAB_BITS(buf, 7, 0, 7);
+
+ e->port_id = get_unaligned_le64(buf + 8);
+
+ /* not specified, but the spec's tendency is little endian */
+ e->manufacture_id = get_unaligned_le16(buf + 16);
+ e->product_id = get_unaligned_le16(buf + 18);
+
+ if (mnl > ELD_MAX_MNL) {
+ codec_info(codec, "HDMI: MNL is reserved value %d\n", mnl);
+ goto out_fail;
+ } else if (ELD_FIXED_BYTES + mnl > size) {
+ codec_info(codec, "HDMI: out of range MNL %d\n", mnl);
+ goto out_fail;
+ } else
+ strlcpy(e->monitor_name, buf + ELD_FIXED_BYTES, mnl + 1);
+
+ for (i = 0; i < e->sad_count; i++) {
+ if (ELD_FIXED_BYTES + mnl + 3 * (i + 1) > size) {
+ codec_info(codec, "HDMI: out of range SAD %d\n", i);
+ goto out_fail;
+ }
+ hdmi_update_short_audio_desc(codec, e->sad + i,
+ buf + ELD_FIXED_BYTES + mnl + 3 * i);
+ }
+
+ /*
+ * HDMI sink's ELD info cannot always be retrieved for now, e.g.
+ * in console or for audio devices. Assume the highest speakers
+ * configuration, to _not_ prohibit multi-channel audio playback.
+ */
+ if (!e->spk_alloc)
+ e->spk_alloc = 0xffff;
+
+ return 0;
+
+out_fail:
+ return -EINVAL;
+}
+
+int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_HDMI_DIP_SIZE,
+ AC_DIPSIZE_ELD_BUF);
+}
+
+int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size)
+{
+ int i;
+ int ret = 0;
+ int size;
+
+ /*
+ * ELD size is initialized to zero in caller function. If no errors and
+ * ELD is valid, actual eld_size is assigned.
+ */
+
+ size = snd_hdmi_get_eld_size(codec, nid);
+ if (size == 0) {
+ /* wfg: workaround for ASUS P5E-VM HDMI board */
+ codec_info(codec, "HDMI: ELD buf size is 0, force 128\n");
+ size = 128;
+ }
+ if (size < ELD_FIXED_BYTES || size > ELD_MAX_SIZE) {
+ codec_info(codec, "HDMI: invalid ELD buf size %d\n", size);
+ return -ERANGE;
+ }
+
+ /* set ELD buffer */
+ for (i = 0; i < size; i++) {
+ unsigned int val = hdmi_get_eld_data(codec, nid, i);
+ /*
+ * Graphics driver might be writing to ELD buffer right now.
+ * Just abort. The caller will repoll after a while.
+ */
+ if (!(val & AC_ELDD_ELD_VALID)) {
+ codec_info(codec, "HDMI: invalid ELD data byte %d\n", i);
+ ret = -EINVAL;
+ goto error;
+ }
+ val &= AC_ELDD_ELD_DATA;
+ /*
+ * The first byte cannot be zero. This can happen on some DVI
+ * connections. Some Intel chips may also need some 250ms delay
+ * to return non-zero ELD data, even when the graphics driver
+ * correctly writes ELD content before setting ELD_valid bit.
+ */
+ if (!val && !i) {
+ codec_dbg(codec, "HDMI: 0 ELD data\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ buf[i] = val;
+ }
+
+ *eld_size = size;
+error:
+ return ret;
+}
+
+/*
+ * SNDRV_PCM_RATE_* and AC_PAR_PCM values don't match, print correct rates with
+ * hdmi-specific routine.
+ */
+static void hdmi_print_pcm_rates(int pcm, char *buf, int buflen)
+{
+ static const unsigned int alsa_rates[] = {
+ 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
+ 88200, 96000, 176400, 192000, 384000
+ };
+ int i, j;
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(alsa_rates); i++)
+ if (pcm & (1 << i))
+ j += scnprintf(buf + j, buflen - j, " %d",
+ alsa_rates[i]);
+
+ buf[j] = '\0'; /* necessary when j == 0 */
+}
+
+#define SND_PRINT_RATES_ADVISED_BUFSIZE 80
+
+static void hdmi_show_short_audio_desc(struct hda_codec *codec,
+ struct cea_sad *a)
+{
+ char buf[SND_PRINT_RATES_ADVISED_BUFSIZE];
+ char buf2[8 + SND_PRINT_BITS_ADVISED_BUFSIZE] = ", bits =";
+
+ if (!a->format)
+ return;
+
+ hdmi_print_pcm_rates(a->rates, buf, sizeof(buf));
+
+ if (a->format == AUDIO_CODING_TYPE_LPCM)
+ snd_print_pcm_bits(a->sample_bits, buf2 + 8, sizeof(buf2) - 8);
+ else if (a->max_bitrate)
+ snprintf(buf2, sizeof(buf2),
+ ", max bitrate = %d", a->max_bitrate);
+ else
+ buf2[0] = '\0';
+
+ codec_dbg(codec,
+ "HDMI: supports coding type %s: channels = %d, rates =%s%s\n",
+ cea_audio_coding_type_names[a->format],
+ a->channels, buf, buf2);
+}
+
+void snd_hdmi_show_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e)
+{
+ int i;
+
+ codec_dbg(codec, "HDMI: detected monitor %s at connection type %s\n",
+ e->monitor_name,
+ eld_connection_type_names[e->conn_type]);
+
+ if (e->spk_alloc) {
+ char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
+ snd_hdac_print_channel_allocation(e->spk_alloc, buf, sizeof(buf));
+ codec_dbg(codec, "HDMI: available speakers:%s\n", buf);
+ }
+
+ for (i = 0; i < e->sad_count; i++)
+ hdmi_show_short_audio_desc(codec, e->sad + i);
+}
+
+#ifdef CONFIG_SND_PROC_FS
+
+static void hdmi_print_sad_info(int i, struct cea_sad *a,
+ struct snd_info_buffer *buffer)
+{
+ char buf[SND_PRINT_RATES_ADVISED_BUFSIZE];
+
+ snd_iprintf(buffer, "sad%d_coding_type\t[0x%x] %s\n",
+ i, a->format, cea_audio_coding_type_names[a->format]);
+ snd_iprintf(buffer, "sad%d_channels\t\t%d\n", i, a->channels);
+
+ hdmi_print_pcm_rates(a->rates, buf, sizeof(buf));
+ snd_iprintf(buffer, "sad%d_rates\t\t[0x%x]%s\n", i, a->rates, buf);
+
+ if (a->format == AUDIO_CODING_TYPE_LPCM) {
+ snd_print_pcm_bits(a->sample_bits, buf, sizeof(buf));
+ snd_iprintf(buffer, "sad%d_bits\t\t[0x%x]%s\n",
+ i, a->sample_bits, buf);
+ }
+
+ if (a->max_bitrate)
+ snd_iprintf(buffer, "sad%d_max_bitrate\t%d\n",
+ i, a->max_bitrate);
+
+ if (a->profile)
+ snd_iprintf(buffer, "sad%d_profile\t\t%d\n", i, a->profile);
+}
+
+void snd_hdmi_print_eld_info(struct hdmi_eld *eld,
+ struct snd_info_buffer *buffer)
+{
+ struct parsed_hdmi_eld *e = &eld->info;
+ char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
+ int i;
+ static const char * const eld_version_names[32] = {
+ "reserved",
+ "reserved",
+ "CEA-861D or below",
+ [3 ... 30] = "reserved",
+ [31] = "partial"
+ };
+ static const char * const cea_edid_version_names[8] = {
+ "no CEA EDID Timing Extension block present",
+ "CEA-861",
+ "CEA-861-A",
+ "CEA-861-B, C or D",
+ [4 ... 7] = "reserved"
+ };
+
+ snd_iprintf(buffer, "monitor_present\t\t%d\n", eld->monitor_present);
+ snd_iprintf(buffer, "eld_valid\t\t%d\n", eld->eld_valid);
+ if (!eld->eld_valid)
+ return;
+ snd_iprintf(buffer, "monitor_name\t\t%s\n", e->monitor_name);
+ snd_iprintf(buffer, "connection_type\t\t%s\n",
+ eld_connection_type_names[e->conn_type]);
+ snd_iprintf(buffer, "eld_version\t\t[0x%x] %s\n", e->eld_ver,
+ eld_version_names[e->eld_ver]);
+ snd_iprintf(buffer, "edid_version\t\t[0x%x] %s\n", e->cea_edid_ver,
+ cea_edid_version_names[e->cea_edid_ver]);
+ snd_iprintf(buffer, "manufacture_id\t\t0x%x\n", e->manufacture_id);
+ snd_iprintf(buffer, "product_id\t\t0x%x\n", e->product_id);
+ snd_iprintf(buffer, "port_id\t\t\t0x%llx\n", (long long)e->port_id);
+ snd_iprintf(buffer, "support_hdcp\t\t%d\n", e->support_hdcp);
+ snd_iprintf(buffer, "support_ai\t\t%d\n", e->support_ai);
+ snd_iprintf(buffer, "audio_sync_delay\t%d\n", e->aud_synch_delay);
+
+ snd_hdac_print_channel_allocation(e->spk_alloc, buf, sizeof(buf));
+ snd_iprintf(buffer, "speakers\t\t[0x%x]%s\n", e->spk_alloc, buf);
+
+ snd_iprintf(buffer, "sad_count\t\t%d\n", e->sad_count);
+
+ for (i = 0; i < e->sad_count; i++)
+ hdmi_print_sad_info(i, e->sad + i, buffer);
+}
+
+void snd_hdmi_write_eld_info(struct hdmi_eld *eld,
+ struct snd_info_buffer *buffer)
+{
+ struct parsed_hdmi_eld *e = &eld->info;
+ char line[64];
+ char name[64];
+ char *sname;
+ long long val;
+ unsigned int n;
+
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%s %llx", name, &val) != 2)
+ continue;
+ /*
+ * We don't allow modification to these fields:
+ * monitor_name manufacture_id product_id
+ * eld_version edid_version
+ */
+ if (!strcmp(name, "monitor_present"))
+ eld->monitor_present = val;
+ else if (!strcmp(name, "eld_valid"))
+ eld->eld_valid = val;
+ else if (!strcmp(name, "connection_type"))
+ e->conn_type = val;
+ else if (!strcmp(name, "port_id"))
+ e->port_id = val;
+ else if (!strcmp(name, "support_hdcp"))
+ e->support_hdcp = val;
+ else if (!strcmp(name, "support_ai"))
+ e->support_ai = val;
+ else if (!strcmp(name, "audio_sync_delay"))
+ e->aud_synch_delay = val;
+ else if (!strcmp(name, "speakers"))
+ e->spk_alloc = val;
+ else if (!strcmp(name, "sad_count"))
+ e->sad_count = val;
+ else if (!strncmp(name, "sad", 3)) {
+ sname = name + 4;
+ n = name[3] - '0';
+ if (name[4] >= '0' && name[4] <= '9') {
+ sname++;
+ n = 10 * n + name[4] - '0';
+ }
+ if (n >= ELD_MAX_SAD)
+ continue;
+ if (!strcmp(sname, "_coding_type"))
+ e->sad[n].format = val;
+ else if (!strcmp(sname, "_channels"))
+ e->sad[n].channels = val;
+ else if (!strcmp(sname, "_rates"))
+ e->sad[n].rates = val;
+ else if (!strcmp(sname, "_bits"))
+ e->sad[n].sample_bits = val;
+ else if (!strcmp(sname, "_max_bitrate"))
+ e->sad[n].max_bitrate = val;
+ else if (!strcmp(sname, "_profile"))
+ e->sad[n].profile = val;
+ if (n >= e->sad_count)
+ e->sad_count = n + 1;
+ }
+ }
+}
+#endif /* CONFIG_SND_PROC_FS */
+
+/* update PCM info based on ELD */
+void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e,
+ struct hda_pcm_stream *hinfo)
+{
+ u32 rates;
+ u64 formats;
+ unsigned int maxbps;
+ unsigned int channels_max;
+ int i;
+
+ /* assume basic audio support (the basic audio flag is not in ELD;
+ * however, all audio capable sinks are required to support basic
+ * audio) */
+ rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000;
+ formats = SNDRV_PCM_FMTBIT_S16_LE;
+ maxbps = 16;
+ channels_max = 2;
+ for (i = 0; i < e->sad_count; i++) {
+ struct cea_sad *a = &e->sad[i];
+ rates |= a->rates;
+ if (a->channels > channels_max)
+ channels_max = a->channels;
+ if (a->format == AUDIO_CODING_TYPE_LPCM) {
+ if (a->sample_bits & AC_SUPPCM_BITS_20) {
+ formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ if (maxbps < 20)
+ maxbps = 20;
+ }
+ if (a->sample_bits & AC_SUPPCM_BITS_24) {
+ formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ if (maxbps < 24)
+ maxbps = 24;
+ }
+ }
+ }
+
+ /* restrict the parameters by the values the codec provides */
+ hinfo->rates &= rates;
+ hinfo->formats &= formats;
+ hinfo->maxbps = min(hinfo->maxbps, maxbps);
+ hinfo->channels_max = min(hinfo->channels_max, channels_max);
+}
+
+
+/* ATI/AMD specific stuff (ELD emulation) */
+
+#define ATI_VERB_SET_AUDIO_DESCRIPTOR 0x776
+#define ATI_VERB_SET_SINK_INFO_INDEX 0x780
+#define ATI_VERB_GET_SPEAKER_ALLOCATION 0xf70
+#define ATI_VERB_GET_AUDIO_DESCRIPTOR 0xf76
+#define ATI_VERB_GET_AUDIO_VIDEO_DELAY 0xf7b
+#define ATI_VERB_GET_SINK_INFO_INDEX 0xf80
+#define ATI_VERB_GET_SINK_INFO_DATA 0xf81
+
+#define ATI_SPKALLOC_SPKALLOC 0x007f
+#define ATI_SPKALLOC_TYPE_HDMI 0x0100
+#define ATI_SPKALLOC_TYPE_DISPLAYPORT 0x0200
+
+/* first three bytes are just standard SAD */
+#define ATI_AUDIODESC_CHANNELS 0x00000007
+#define ATI_AUDIODESC_RATES 0x0000ff00
+#define ATI_AUDIODESC_LPCM_STEREO_RATES 0xff000000
+
+/* in standard HDMI VSDB format */
+#define ATI_DELAY_VIDEO_LATENCY 0x000000ff
+#define ATI_DELAY_AUDIO_LATENCY 0x0000ff00
+
+enum ati_sink_info_idx {
+ ATI_INFO_IDX_MANUFACTURER_ID = 0,
+ ATI_INFO_IDX_PRODUCT_ID = 1,
+ ATI_INFO_IDX_SINK_DESC_LEN = 2,
+ ATI_INFO_IDX_PORT_ID_LOW = 3,
+ ATI_INFO_IDX_PORT_ID_HIGH = 4,
+ ATI_INFO_IDX_SINK_DESC_FIRST = 5,
+ ATI_INFO_IDX_SINK_DESC_LAST = 22, /* max len 18 bytes */
+};
+
+int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size, bool rev3_or_later)
+{
+ int spkalloc, ati_sad, aud_synch;
+ int sink_desc_len = 0;
+ int pos, i;
+
+ /* ATI/AMD does not have ELD, emulate it */
+
+ spkalloc = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SPEAKER_ALLOCATION, 0);
+
+ if (spkalloc <= 0) {
+ codec_info(codec, "HDMI ATI/AMD: no speaker allocation for ELD\n");
+ return -EINVAL;
+ }
+
+ memset(buf, 0, ELD_FIXED_BYTES + ELD_MAX_MNL + ELD_MAX_SAD * 3);
+
+ /* version */
+ buf[0] = ELD_VER_CEA_861D << 3;
+
+ /* speaker allocation from EDID */
+ buf[7] = spkalloc & ATI_SPKALLOC_SPKALLOC;
+
+ /* is DisplayPort? */
+ if (spkalloc & ATI_SPKALLOC_TYPE_DISPLAYPORT)
+ buf[5] |= 0x04;
+
+ pos = ELD_FIXED_BYTES;
+
+ if (rev3_or_later) {
+ int sink_info;
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_LOW);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le32(sink_info, buf + 8);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_HIGH);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le32(sink_info, buf + 12);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_MANUFACTURER_ID);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le16(sink_info, buf + 16);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PRODUCT_ID);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le16(sink_info, buf + 18);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_LEN);
+ sink_desc_len = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+
+ if (sink_desc_len > ELD_MAX_MNL) {
+ codec_info(codec, "HDMI ATI/AMD: Truncating HDMI sink description with length %d\n",
+ sink_desc_len);
+ sink_desc_len = ELD_MAX_MNL;
+ }
+
+ buf[4] |= sink_desc_len;
+
+ for (i = 0; i < sink_desc_len; i++) {
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_FIRST + i);
+ buf[pos++] = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ }
+ }
+
+ for (i = AUDIO_CODING_TYPE_LPCM; i <= AUDIO_CODING_TYPE_WMAPRO; i++) {
+ if (i == AUDIO_CODING_TYPE_SACD || i == AUDIO_CODING_TYPE_DST)
+ continue; /* not handled by ATI/AMD */
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_AUDIO_DESCRIPTOR, i << 3);
+ ati_sad = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_DESCRIPTOR, 0);
+
+ if (ati_sad <= 0)
+ continue;
+
+ if (ati_sad & ATI_AUDIODESC_RATES) {
+ /* format is supported, copy SAD as-is */
+ buf[pos++] = (ati_sad & 0x0000ff) >> 0;
+ buf[pos++] = (ati_sad & 0x00ff00) >> 8;
+ buf[pos++] = (ati_sad & 0xff0000) >> 16;
+ }
+
+ if (i == AUDIO_CODING_TYPE_LPCM
+ && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES)
+ && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES) >> 16 != (ati_sad & ATI_AUDIODESC_RATES)) {
+ /* for PCM there is a separate stereo rate mask */
+ buf[pos++] = ((ati_sad & 0x000000ff) & ~ATI_AUDIODESC_CHANNELS) | 0x1;
+ /* rates from the extra byte */
+ buf[pos++] = (ati_sad & 0xff000000) >> 24;
+ buf[pos++] = (ati_sad & 0x00ff0000) >> 16;
+ }
+ }
+
+ if (pos == ELD_FIXED_BYTES + sink_desc_len) {
+ codec_info(codec, "HDMI ATI/AMD: no audio descriptors for ELD\n");
+ return -EINVAL;
+ }
+
+ /*
+ * HDMI VSDB latency format:
+ * separately for both audio and video:
+ * 0 field not valid or unknown latency
+ * [1..251] msecs = (x-1)*2 (max 500ms with x = 251 = 0xfb)
+ * 255 audio/video not supported
+ *
+ * HDA latency format:
+ * single value indicating video latency relative to audio:
+ * 0 unknown or 0ms
+ * [1..250] msecs = x*2 (max 500ms with x = 250 = 0xfa)
+ * [251..255] reserved
+ */
+ aud_synch = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_VIDEO_DELAY, 0);
+ if ((aud_synch & ATI_DELAY_VIDEO_LATENCY) && (aud_synch & ATI_DELAY_AUDIO_LATENCY)) {
+ int video_latency_hdmi = (aud_synch & ATI_DELAY_VIDEO_LATENCY);
+ int audio_latency_hdmi = (aud_synch & ATI_DELAY_AUDIO_LATENCY) >> 8;
+
+ if (video_latency_hdmi <= 0xfb && audio_latency_hdmi <= 0xfb &&
+ video_latency_hdmi > audio_latency_hdmi)
+ buf[6] = video_latency_hdmi - audio_latency_hdmi;
+ /* else unknown/invalid or 0ms or video ahead of audio, so use zero */
+ }
+
+ /* SAD count */
+ buf[5] |= ((pos - ELD_FIXED_BYTES - sink_desc_len) / 3) << 4;
+
+ /* Baseline ELD block length is 4-byte aligned */
+ pos = round_up(pos, 4);
+
+ /* Baseline ELD length (4-byte header is not counted in) */
+ buf[2] = (pos - 4) / 4;
+
+ *eld_size = pos;
+
+ return 0;
+}
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c
new file mode 100644
index 000000000..35113fa84
--- /dev/null
+++ b/sound/pci/hda/hda_generic.c
@@ -0,0 +1,6227 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Generic widget tree parser
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/sort.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+#include "hda_beep.h"
+#include "hda_generic.h"
+
+
+/**
+ * snd_hda_gen_spec_init - initialize hda_gen_spec struct
+ * @spec: hda_gen_spec object to initialize
+ *
+ * Initialize the given hda_gen_spec object.
+ */
+int snd_hda_gen_spec_init(struct hda_gen_spec *spec)
+{
+ snd_array_init(&spec->kctls, sizeof(struct snd_kcontrol_new), 32);
+ snd_array_init(&spec->paths, sizeof(struct nid_path), 8);
+ snd_array_init(&spec->loopback_list, sizeof(struct hda_amp_list), 8);
+ mutex_init(&spec->pcm_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_spec_init);
+
+/**
+ * snd_hda_gen_add_kctl - Add a new kctl_new struct from the template
+ * @spec: hda_gen_spec object
+ * @name: name string to override the template, NULL if unchanged
+ * @temp: template for the new kctl
+ *
+ * Add a new kctl (actually snd_kcontrol_new to be instantiated later)
+ * element based on the given snd_kcontrol_new template @temp and the
+ * name string @name to the list in @spec.
+ * Returns the newly created object or NULL as error.
+ */
+struct snd_kcontrol_new *
+snd_hda_gen_add_kctl(struct hda_gen_spec *spec, const char *name,
+ const struct snd_kcontrol_new *temp)
+{
+ struct snd_kcontrol_new *knew = snd_array_new(&spec->kctls);
+ if (!knew)
+ return NULL;
+ *knew = *temp;
+ if (name)
+ knew->name = kstrdup(name, GFP_KERNEL);
+ else if (knew->name)
+ knew->name = kstrdup(knew->name, GFP_KERNEL);
+ if (!knew->name)
+ return NULL;
+ return knew;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_add_kctl);
+
+static void free_kctls(struct hda_gen_spec *spec)
+{
+ if (spec->kctls.list) {
+ struct snd_kcontrol_new *kctl = spec->kctls.list;
+ int i;
+ for (i = 0; i < spec->kctls.used; i++)
+ kfree(kctl[i].name);
+ }
+ snd_array_free(&spec->kctls);
+}
+
+static void snd_hda_gen_spec_free(struct hda_gen_spec *spec)
+{
+ if (!spec)
+ return;
+ free_kctls(spec);
+ snd_array_free(&spec->paths);
+ snd_array_free(&spec->loopback_list);
+#ifdef CONFIG_SND_HDA_GENERIC_LEDS
+ if (spec->led_cdevs[LED_AUDIO_MUTE])
+ led_classdev_unregister(spec->led_cdevs[LED_AUDIO_MUTE]);
+ if (spec->led_cdevs[LED_AUDIO_MICMUTE])
+ led_classdev_unregister(spec->led_cdevs[LED_AUDIO_MICMUTE]);
+#endif
+}
+
+/*
+ * store user hints
+ */
+static void parse_user_hints(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int val;
+
+ val = snd_hda_get_bool_hint(codec, "jack_detect");
+ if (val >= 0)
+ codec->no_jack_detect = !val;
+ val = snd_hda_get_bool_hint(codec, "inv_jack_detect");
+ if (val >= 0)
+ codec->inv_jack_detect = !!val;
+ val = snd_hda_get_bool_hint(codec, "trigger_sense");
+ if (val >= 0)
+ codec->no_trigger_sense = !val;
+ val = snd_hda_get_bool_hint(codec, "inv_eapd");
+ if (val >= 0)
+ codec->inv_eapd = !!val;
+ val = snd_hda_get_bool_hint(codec, "pcm_format_first");
+ if (val >= 0)
+ codec->pcm_format_first = !!val;
+ val = snd_hda_get_bool_hint(codec, "sticky_stream");
+ if (val >= 0)
+ codec->no_sticky_stream = !val;
+ val = snd_hda_get_bool_hint(codec, "spdif_status_reset");
+ if (val >= 0)
+ codec->spdif_status_reset = !!val;
+ val = snd_hda_get_bool_hint(codec, "pin_amp_workaround");
+ if (val >= 0)
+ codec->pin_amp_workaround = !!val;
+ val = snd_hda_get_bool_hint(codec, "single_adc_amp");
+ if (val >= 0)
+ codec->single_adc_amp = !!val;
+ val = snd_hda_get_bool_hint(codec, "power_save_node");
+ if (val >= 0)
+ codec->power_save_node = !!val;
+
+ val = snd_hda_get_bool_hint(codec, "auto_mute");
+ if (val >= 0)
+ spec->suppress_auto_mute = !val;
+ val = snd_hda_get_bool_hint(codec, "auto_mic");
+ if (val >= 0)
+ spec->suppress_auto_mic = !val;
+ val = snd_hda_get_bool_hint(codec, "line_in_auto_switch");
+ if (val >= 0)
+ spec->line_in_auto_switch = !!val;
+ val = snd_hda_get_bool_hint(codec, "auto_mute_via_amp");
+ if (val >= 0)
+ spec->auto_mute_via_amp = !!val;
+ val = snd_hda_get_bool_hint(codec, "need_dac_fix");
+ if (val >= 0)
+ spec->need_dac_fix = !!val;
+ val = snd_hda_get_bool_hint(codec, "primary_hp");
+ if (val >= 0)
+ spec->no_primary_hp = !val;
+ val = snd_hda_get_bool_hint(codec, "multi_io");
+ if (val >= 0)
+ spec->no_multi_io = !val;
+ val = snd_hda_get_bool_hint(codec, "multi_cap_vol");
+ if (val >= 0)
+ spec->multi_cap_vol = !!val;
+ val = snd_hda_get_bool_hint(codec, "inv_dmic_split");
+ if (val >= 0)
+ spec->inv_dmic_split = !!val;
+ val = snd_hda_get_bool_hint(codec, "indep_hp");
+ if (val >= 0)
+ spec->indep_hp = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input");
+ if (val >= 0)
+ spec->add_stereo_mix_input = !!val;
+ /* the following two are just for compatibility */
+ val = snd_hda_get_bool_hint(codec, "add_out_jack_modes");
+ if (val >= 0)
+ spec->add_jack_modes = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_in_jack_modes");
+ if (val >= 0)
+ spec->add_jack_modes = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_jack_modes");
+ if (val >= 0)
+ spec->add_jack_modes = !!val;
+ val = snd_hda_get_bool_hint(codec, "power_down_unused");
+ if (val >= 0)
+ spec->power_down_unused = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_hp_mic");
+ if (val >= 0)
+ spec->hp_mic = !!val;
+ val = snd_hda_get_bool_hint(codec, "hp_mic_detect");
+ if (val >= 0)
+ spec->suppress_hp_mic_detect = !val;
+ val = snd_hda_get_bool_hint(codec, "vmaster");
+ if (val >= 0)
+ spec->suppress_vmaster = !val;
+
+ if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
+ spec->mixer_nid = val;
+}
+
+/*
+ * pin control value accesses
+ */
+
+#define update_pin_ctl(codec, pin, val) \
+ snd_hda_codec_write_cache(codec, pin, 0, \
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val)
+
+/* restore the pinctl based on the cached value */
+static inline void restore_pin_ctl(struct hda_codec *codec, hda_nid_t pin)
+{
+ update_pin_ctl(codec, pin, snd_hda_codec_get_pin_target(codec, pin));
+}
+
+/* set the pinctl target value and write it if requested */
+static void set_pin_target(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val, bool do_write)
+{
+ if (!pin)
+ return;
+ val = snd_hda_correct_pin_ctl(codec, pin, val);
+ snd_hda_codec_set_pin_target(codec, pin, val);
+ if (do_write)
+ update_pin_ctl(codec, pin, val);
+}
+
+/* set pinctl target values for all given pins */
+static void set_pin_targets(struct hda_codec *codec, int num_pins,
+ hda_nid_t *pins, unsigned int val)
+{
+ int i;
+ for (i = 0; i < num_pins; i++)
+ set_pin_target(codec, pins[i], val, false);
+}
+
+/*
+ * parsing paths
+ */
+
+/* return the position of NID in the list, or -1 if not found */
+static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
+{
+ int i;
+ for (i = 0; i < nums; i++)
+ if (list[i] == nid)
+ return i;
+ return -1;
+}
+
+/* return true if the given NID is contained in the path */
+static bool is_nid_contained(struct nid_path *path, hda_nid_t nid)
+{
+ return find_idx_in_nid_list(nid, path->path, path->depth) >= 0;
+}
+
+static struct nid_path *get_nid_path(struct hda_codec *codec,
+ hda_nid_t from_nid, hda_nid_t to_nid,
+ int anchor_nid)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+ int i;
+
+ snd_array_for_each(&spec->paths, i, path) {
+ if (path->depth <= 0)
+ continue;
+ if ((!from_nid || path->path[0] == from_nid) &&
+ (!to_nid || path->path[path->depth - 1] == to_nid)) {
+ if (!anchor_nid ||
+ (anchor_nid > 0 && is_nid_contained(path, anchor_nid)) ||
+ (anchor_nid < 0 && !is_nid_contained(path, anchor_nid)))
+ return path;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * snd_hda_get_path_idx - get the index number corresponding to the path
+ * instance
+ * @codec: the HDA codec
+ * @path: nid_path object
+ *
+ * The returned index starts from 1, i.e. the actual array index with offset 1,
+ * and zero is handled as an invalid path
+ */
+int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *array = spec->paths.list;
+ ssize_t idx;
+
+ if (!spec->paths.used)
+ return 0;
+ idx = path - array;
+ if (idx < 0 || idx >= spec->paths.used)
+ return 0;
+ return idx + 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_path_idx);
+
+/**
+ * snd_hda_get_path_from_idx - get the path instance corresponding to the
+ * given index number
+ * @codec: the HDA codec
+ * @idx: the path index
+ */
+struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (idx <= 0 || idx > spec->paths.used)
+ return NULL;
+ return snd_array_elem(&spec->paths, idx - 1);
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_path_from_idx);
+
+/* check whether the given DAC is already found in any existing paths */
+static bool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct nid_path *path;
+ int i;
+
+ snd_array_for_each(&spec->paths, i, path) {
+ if (path->path[0] == nid)
+ return true;
+ }
+ return false;
+}
+
+/* check whether the given two widgets can be connected */
+static bool is_reachable_path(struct hda_codec *codec,
+ hda_nid_t from_nid, hda_nid_t to_nid)
+{
+ if (!from_nid || !to_nid)
+ return false;
+ return snd_hda_get_conn_index(codec, to_nid, from_nid, true) >= 0;
+}
+
+/* nid, dir and idx */
+#define AMP_VAL_COMPARE_MASK (0xffff | (1U << 18) | (0x0f << 19))
+
+/* check whether the given ctl is already assigned in any path elements */
+static bool is_ctl_used(struct hda_codec *codec, unsigned int val, int type)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct nid_path *path;
+ int i;
+
+ val &= AMP_VAL_COMPARE_MASK;
+ snd_array_for_each(&spec->paths, i, path) {
+ if ((path->ctls[type] & AMP_VAL_COMPARE_MASK) == val)
+ return true;
+ }
+ return false;
+}
+
+/* check whether a control with the given (nid, dir, idx) was assigned */
+static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx, int type)
+{
+ unsigned int val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir);
+ return is_ctl_used(codec, val, type);
+}
+
+static void print_nid_path(struct hda_codec *codec,
+ const char *pfx, struct nid_path *path)
+{
+ char buf[40];
+ char *pos = buf;
+ int i;
+
+ *pos = 0;
+ for (i = 0; i < path->depth; i++)
+ pos += scnprintf(pos, sizeof(buf) - (pos - buf), "%s%02x",
+ pos != buf ? ":" : "",
+ path->path[i]);
+
+ codec_dbg(codec, "%s path: depth=%d '%s'\n", pfx, path->depth, buf);
+}
+
+/* called recursively */
+static bool __parse_nid_path(struct hda_codec *codec,
+ hda_nid_t from_nid, hda_nid_t to_nid,
+ int anchor_nid, struct nid_path *path,
+ int depth)
+{
+ const hda_nid_t *conn;
+ int i, nums;
+
+ if (to_nid == anchor_nid)
+ anchor_nid = 0; /* anchor passed */
+ else if (to_nid == (hda_nid_t)(-anchor_nid))
+ return false; /* hit the exclusive nid */
+
+ nums = snd_hda_get_conn_list(codec, to_nid, &conn);
+ for (i = 0; i < nums; i++) {
+ if (conn[i] != from_nid) {
+ /* special case: when from_nid is 0,
+ * try to find an empty DAC
+ */
+ if (from_nid ||
+ get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT ||
+ is_dac_already_used(codec, conn[i]))
+ continue;
+ }
+ /* anchor is not requested or already passed? */
+ if (anchor_nid <= 0)
+ goto found;
+ }
+ if (depth >= MAX_NID_PATH_DEPTH)
+ return false;
+ for (i = 0; i < nums; i++) {
+ unsigned int type;
+ type = get_wcaps_type(get_wcaps(codec, conn[i]));
+ if (type == AC_WID_AUD_OUT || type == AC_WID_AUD_IN ||
+ type == AC_WID_PIN)
+ continue;
+ if (__parse_nid_path(codec, from_nid, conn[i],
+ anchor_nid, path, depth + 1))
+ goto found;
+ }
+ return false;
+
+ found:
+ path->path[path->depth] = conn[i];
+ path->idx[path->depth + 1] = i;
+ if (nums > 1 && get_wcaps_type(get_wcaps(codec, to_nid)) != AC_WID_AUD_MIX)
+ path->multi[path->depth + 1] = 1;
+ path->depth++;
+ return true;
+}
+
+/*
+ * snd_hda_parse_nid_path - parse the widget path from the given nid to
+ * the target nid
+ * @codec: the HDA codec
+ * @from_nid: the NID where the path start from
+ * @to_nid: the NID where the path ends at
+ * @anchor_nid: the anchor indication
+ * @path: the path object to store the result
+ *
+ * Returns true if a matching path is found.
+ *
+ * The parsing behavior depends on parameters:
+ * when @from_nid is 0, try to find an empty DAC;
+ * when @anchor_nid is set to a positive value, only paths through the widget
+ * with the given value are evaluated.
+ * when @anchor_nid is set to a negative value, paths through the widget
+ * with the negative of given value are excluded, only other paths are chosen.
+ * when @anchor_nid is zero, no special handling about path selection.
+ */
+static bool snd_hda_parse_nid_path(struct hda_codec *codec, hda_nid_t from_nid,
+ hda_nid_t to_nid, int anchor_nid,
+ struct nid_path *path)
+{
+ if (__parse_nid_path(codec, from_nid, to_nid, anchor_nid, path, 1)) {
+ path->path[path->depth] = to_nid;
+ path->depth++;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * snd_hda_add_new_path - parse the path between the given NIDs and
+ * add to the path list
+ * @codec: the HDA codec
+ * @from_nid: the NID where the path start from
+ * @to_nid: the NID where the path ends at
+ * @anchor_nid: the anchor indication, see snd_hda_parse_nid_path()
+ *
+ * If no valid path is found, returns NULL.
+ */
+struct nid_path *
+snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid,
+ hda_nid_t to_nid, int anchor_nid)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+
+ if (from_nid && to_nid && !is_reachable_path(codec, from_nid, to_nid))
+ return NULL;
+
+ /* check whether the path has been already added */
+ path = get_nid_path(codec, from_nid, to_nid, anchor_nid);
+ if (path)
+ return path;
+
+ path = snd_array_new(&spec->paths);
+ if (!path)
+ return NULL;
+ memset(path, 0, sizeof(*path));
+ if (snd_hda_parse_nid_path(codec, from_nid, to_nid, anchor_nid, path))
+ return path;
+ /* push back */
+ spec->paths.used--;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_new_path);
+
+/* clear the given path as invalid so that it won't be picked up later */
+static void invalidate_nid_path(struct hda_codec *codec, int idx)
+{
+ struct nid_path *path = snd_hda_get_path_from_idx(codec, idx);
+ if (!path)
+ return;
+ memset(path, 0, sizeof(*path));
+}
+
+/* return a DAC if paired to the given pin by codec driver */
+static hda_nid_t get_preferred_dac(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const hda_nid_t *list = spec->preferred_dacs;
+
+ if (!list)
+ return 0;
+ for (; *list; list += 2)
+ if (*list == pin)
+ return list[1];
+ return 0;
+}
+
+/* look for an empty DAC slot */
+static hda_nid_t look_for_dac(struct hda_codec *codec, hda_nid_t pin,
+ bool is_digital)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ bool cap_digital;
+ int i;
+
+ for (i = 0; i < spec->num_all_dacs; i++) {
+ hda_nid_t nid = spec->all_dacs[i];
+ if (!nid || is_dac_already_used(codec, nid))
+ continue;
+ cap_digital = !!(get_wcaps(codec, nid) & AC_WCAP_DIGITAL);
+ if (is_digital != cap_digital)
+ continue;
+ if (is_reachable_path(codec, nid, pin))
+ return nid;
+ }
+ return 0;
+}
+
+/* replace the channels in the composed amp value with the given number */
+static unsigned int amp_val_replace_channels(unsigned int val, unsigned int chs)
+{
+ val &= ~(0x3U << 16);
+ val |= chs << 16;
+ return val;
+}
+
+static bool same_amp_caps(struct hda_codec *codec, hda_nid_t nid1,
+ hda_nid_t nid2, int dir)
+{
+ if (!(get_wcaps(codec, nid1) & (1 << (dir + 1))))
+ return !(get_wcaps(codec, nid2) & (1 << (dir + 1)));
+ return (query_amp_caps(codec, nid1, dir) ==
+ query_amp_caps(codec, nid2, dir));
+}
+
+/* look for a widget suitable for assigning a mute switch in the path */
+static hda_nid_t look_for_out_mute_nid(struct hda_codec *codec,
+ struct nid_path *path)
+{
+ int i;
+
+ for (i = path->depth - 1; i >= 0; i--) {
+ if (nid_has_mute(codec, path->path[i], HDA_OUTPUT))
+ return path->path[i];
+ if (i != path->depth - 1 && i != 0 &&
+ nid_has_mute(codec, path->path[i], HDA_INPUT))
+ return path->path[i];
+ }
+ return 0;
+}
+
+/* look for a widget suitable for assigning a volume ctl in the path */
+static hda_nid_t look_for_out_vol_nid(struct hda_codec *codec,
+ struct nid_path *path)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = path->depth - 1; i >= 0; i--) {
+ hda_nid_t nid = path->path[i];
+ if ((spec->out_vol_mask >> nid) & 1)
+ continue;
+ if (nid_has_volume(codec, nid, HDA_OUTPUT))
+ return nid;
+ }
+ return 0;
+}
+
+/*
+ * path activation / deactivation
+ */
+
+/* can have the amp-in capability? */
+static bool has_amp_in(struct hda_codec *codec, struct nid_path *path, int idx)
+{
+ hda_nid_t nid = path->path[idx];
+ unsigned int caps = get_wcaps(codec, nid);
+ unsigned int type = get_wcaps_type(caps);
+
+ if (!(caps & AC_WCAP_IN_AMP))
+ return false;
+ if (type == AC_WID_PIN && idx > 0) /* only for input pins */
+ return false;
+ return true;
+}
+
+/* can have the amp-out capability? */
+static bool has_amp_out(struct hda_codec *codec, struct nid_path *path, int idx)
+{
+ hda_nid_t nid = path->path[idx];
+ unsigned int caps = get_wcaps(codec, nid);
+ unsigned int type = get_wcaps_type(caps);
+
+ if (!(caps & AC_WCAP_OUT_AMP))
+ return false;
+ if (type == AC_WID_PIN && !idx) /* only for output pins */
+ return false;
+ return true;
+}
+
+/* check whether the given (nid,dir,idx) is active */
+static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int dir, unsigned int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int type = get_wcaps_type(get_wcaps(codec, nid));
+ const struct nid_path *path;
+ int i, n;
+
+ if (nid == codec->core.afg)
+ return true;
+
+ snd_array_for_each(&spec->paths, n, path) {
+ if (!path->active)
+ continue;
+ if (codec->power_save_node) {
+ if (!path->stream_enabled)
+ continue;
+ /* ignore unplugged paths except for DAC/ADC */
+ if (!(path->pin_enabled || path->pin_fixed) &&
+ type != AC_WID_AUD_OUT && type != AC_WID_AUD_IN)
+ continue;
+ }
+ for (i = 0; i < path->depth; i++) {
+ if (path->path[i] == nid) {
+ if (dir == HDA_OUTPUT || idx == -1 ||
+ path->idx[i] == idx)
+ return true;
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+/* check whether the NID is referred by any active paths */
+#define is_active_nid_for_any(codec, nid) \
+ is_active_nid(codec, nid, HDA_OUTPUT, -1)
+
+/* get the default amp value for the target state */
+static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int caps, bool enable)
+{
+ unsigned int val = 0;
+
+ if (caps & AC_AMPCAP_NUM_STEPS) {
+ /* set to 0dB */
+ if (enable)
+ val = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
+ }
+ if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) {
+ if (!enable)
+ val |= HDA_AMP_MUTE;
+ }
+ return val;
+}
+
+/* is this a stereo widget or a stereo-to-mono mix? */
+static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, int dir)
+{
+ unsigned int wcaps = get_wcaps(codec, nid);
+ hda_nid_t conn;
+
+ if (wcaps & AC_WCAP_STEREO)
+ return true;
+ if (dir != HDA_INPUT || get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
+ return false;
+ if (snd_hda_get_num_conns(codec, nid) != 1)
+ return false;
+ if (snd_hda_get_connections(codec, nid, &conn, 1) < 0)
+ return false;
+ return !!(get_wcaps(codec, conn) & AC_WCAP_STEREO);
+}
+
+/* initialize the amp value (only at the first time) */
+static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
+{
+ unsigned int caps = query_amp_caps(codec, nid, dir);
+ int val = get_amp_val_to_activate(codec, nid, dir, caps, false);
+
+ if (is_stereo_amps(codec, nid, dir))
+ snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val);
+ else
+ snd_hda_codec_amp_init(codec, nid, 0, dir, idx, 0xff, val);
+}
+
+/* update the amp, doing in stereo or mono depending on NID */
+static int update_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx,
+ unsigned int mask, unsigned int val)
+{
+ if (is_stereo_amps(codec, nid, dir))
+ return snd_hda_codec_amp_stereo(codec, nid, dir, idx,
+ mask, val);
+ else
+ return snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+ mask, val);
+}
+
+/* calculate amp value mask we can modify;
+ * if the given amp is controlled by mixers, don't touch it
+ */
+static unsigned int get_amp_mask_to_modify(struct hda_codec *codec,
+ hda_nid_t nid, int dir, int idx,
+ unsigned int caps)
+{
+ unsigned int mask = 0xff;
+
+ if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) {
+ if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_MUTE_CTL))
+ mask &= ~0x80;
+ }
+ if (caps & AC_AMPCAP_NUM_STEPS) {
+ if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) ||
+ is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL))
+ mask &= ~0x7f;
+ }
+ return mask;
+}
+
+static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir,
+ int idx, int idx_to_check, bool enable)
+{
+ unsigned int caps;
+ unsigned int mask, val;
+
+ caps = query_amp_caps(codec, nid, dir);
+ val = get_amp_val_to_activate(codec, nid, dir, caps, enable);
+ mask = get_amp_mask_to_modify(codec, nid, dir, idx_to_check, caps);
+ if (!mask)
+ return;
+
+ val &= mask;
+ update_amp(codec, nid, dir, idx, mask, val);
+}
+
+static void check_and_activate_amp(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx, int idx_to_check,
+ bool enable)
+{
+ /* check whether the given amp is still used by others */
+ if (!enable && is_active_nid(codec, nid, dir, idx_to_check))
+ return;
+ activate_amp(codec, nid, dir, idx, idx_to_check, enable);
+}
+
+static void activate_amp_out(struct hda_codec *codec, struct nid_path *path,
+ int i, bool enable)
+{
+ hda_nid_t nid = path->path[i];
+ init_amp(codec, nid, HDA_OUTPUT, 0);
+ check_and_activate_amp(codec, nid, HDA_OUTPUT, 0, 0, enable);
+}
+
+static void activate_amp_in(struct hda_codec *codec, struct nid_path *path,
+ int i, bool enable, bool add_aamix)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const hda_nid_t *conn;
+ int n, nums, idx;
+ int type;
+ hda_nid_t nid = path->path[i];
+
+ nums = snd_hda_get_conn_list(codec, nid, &conn);
+ if (nums < 0)
+ return;
+ type = get_wcaps_type(get_wcaps(codec, nid));
+ if (type == AC_WID_PIN ||
+ (type == AC_WID_AUD_IN && codec->single_adc_amp)) {
+ nums = 1;
+ idx = 0;
+ } else
+ idx = path->idx[i];
+
+ for (n = 0; n < nums; n++)
+ init_amp(codec, nid, HDA_INPUT, n);
+
+ /* here is a little bit tricky in comparison with activate_amp_out();
+ * when aa-mixer is available, we need to enable the path as well
+ */
+ for (n = 0; n < nums; n++) {
+ if (n != idx) {
+ if (conn[n] != spec->mixer_merge_nid)
+ continue;
+ /* when aamix is disabled, force to off */
+ if (!add_aamix) {
+ activate_amp(codec, nid, HDA_INPUT, n, n, false);
+ continue;
+ }
+ }
+ check_and_activate_amp(codec, nid, HDA_INPUT, n, idx, enable);
+ }
+}
+
+/* sync power of each widget in the given path */
+static hda_nid_t path_power_update(struct hda_codec *codec,
+ struct nid_path *path,
+ bool allow_powerdown)
+{
+ hda_nid_t nid, changed = 0;
+ int i, state, power;
+
+ for (i = 0; i < path->depth; i++) {
+ nid = path->path[i];
+ if (!(get_wcaps(codec, nid) & AC_WCAP_POWER))
+ continue;
+ if (nid == codec->core.afg)
+ continue;
+ if (!allow_powerdown || is_active_nid_for_any(codec, nid))
+ state = AC_PWRST_D0;
+ else
+ state = AC_PWRST_D3;
+ power = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_POWER_STATE, 0);
+ if (power != (state | (state << 4))) {
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_POWER_STATE, state);
+ changed = nid;
+ /* all known codecs seem to be capable to handl
+ * widgets state even in D3, so far.
+ * if any new codecs need to restore the widget
+ * states after D0 transition, call the function
+ * below.
+ */
+#if 0 /* disabled */
+ if (state == AC_PWRST_D0)
+ snd_hdac_regmap_sync_node(&codec->core, nid);
+#endif
+ }
+ }
+ return changed;
+}
+
+/* do sync with the last power state change */
+static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid)
+{
+ if (nid) {
+ msleep(10);
+ snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
+ }
+}
+
+/**
+ * snd_hda_activate_path - activate or deactivate the given path
+ * @codec: the HDA codec
+ * @path: the path to activate/deactivate
+ * @enable: flag to activate or not
+ * @add_aamix: enable the input from aamix NID
+ *
+ * If @add_aamix is set, enable the input from aa-mix NID as well (if any).
+ */
+void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path,
+ bool enable, bool add_aamix)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ path->active = enable;
+
+ /* make sure the widget is powered up */
+ if (enable && (spec->power_down_unused || codec->power_save_node))
+ path_power_update(codec, path, codec->power_save_node);
+
+ for (i = path->depth - 1; i >= 0; i--) {
+ hda_nid_t nid = path->path[i];
+
+ if (enable && path->multi[i])
+ snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_CONNECT_SEL,
+ path->idx[i]);
+ if (has_amp_in(codec, path, i))
+ activate_amp_in(codec, path, i, enable, add_aamix);
+ if (has_amp_out(codec, path, i))
+ activate_amp_out(codec, path, i, enable);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_activate_path);
+
+/* if the given path is inactive, put widgets into D3 (only if suitable) */
+static void path_power_down_sync(struct hda_codec *codec, struct nid_path *path)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!(spec->power_down_unused || codec->power_save_node) || path->active)
+ return;
+ sync_power_state_change(codec, path_power_update(codec, path, true));
+}
+
+/* turn on/off EAPD on the given pin */
+static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->own_eapd_ctl ||
+ !(snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD))
+ return;
+ if (spec->keep_eapd_on && !enable)
+ return;
+ if (codec->inv_eapd)
+ enable = !enable;
+ snd_hda_codec_write_cache(codec, pin, 0,
+ AC_VERB_SET_EAPD_BTLENABLE,
+ enable ? 0x02 : 0x00);
+}
+
+/* re-initialize the path specified by the given path index */
+static void resume_path_from_idx(struct hda_codec *codec, int path_idx)
+{
+ struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx);
+ if (path)
+ snd_hda_activate_path(codec, path, path->active, false);
+}
+
+
+/*
+ * Helper functions for creating mixer ctl elements
+ */
+
+static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+enum {
+ HDA_CTL_WIDGET_VOL,
+ HDA_CTL_WIDGET_MUTE,
+ HDA_CTL_BIND_MUTE,
+};
+static const struct snd_kcontrol_new control_templates[] = {
+ HDA_CODEC_VOLUME(NULL, 0, 0, 0),
+ /* only the put callback is replaced for handling the special mute */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .subdevice = HDA_SUBDEV_AMP_FLAG,
+ .info = snd_hda_mixer_amp_switch_info,
+ .get = snd_hda_mixer_amp_switch_get,
+ .put = hda_gen_mixer_mute_put, /* replaced */
+ .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0),
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_hda_mixer_amp_switch_info,
+ .get = hda_gen_bind_mute_get,
+ .put = hda_gen_bind_mute_put, /* replaced */
+ .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0),
+ },
+};
+
+/* add dynamic controls from template */
+static struct snd_kcontrol_new *
+add_control(struct hda_gen_spec *spec, int type, const char *name,
+ int cidx, unsigned long val)
+{
+ struct snd_kcontrol_new *knew;
+
+ knew = snd_hda_gen_add_kctl(spec, name, &control_templates[type]);
+ if (!knew)
+ return NULL;
+ knew->index = cidx;
+ if (get_amp_nid_(val))
+ knew->subdevice = HDA_SUBDEV_AMP_FLAG;
+ knew->private_value = val;
+ return knew;
+}
+
+static int add_control_with_pfx(struct hda_gen_spec *spec, int type,
+ const char *pfx, const char *dir,
+ const char *sfx, int cidx, unsigned long val)
+{
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx);
+ if (!add_control(spec, type, name, cidx, val))
+ return -ENOMEM;
+ return 0;
+}
+
+#define add_pb_vol_ctrl(spec, type, pfx, val) \
+ add_control_with_pfx(spec, type, pfx, "Playback", "Volume", 0, val)
+#define add_pb_sw_ctrl(spec, type, pfx, val) \
+ add_control_with_pfx(spec, type, pfx, "Playback", "Switch", 0, val)
+#define __add_pb_vol_ctrl(spec, type, pfx, cidx, val) \
+ add_control_with_pfx(spec, type, pfx, "Playback", "Volume", cidx, val)
+#define __add_pb_sw_ctrl(spec, type, pfx, cidx, val) \
+ add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val)
+
+static int add_vol_ctl(struct hda_codec *codec, const char *pfx, int cidx,
+ unsigned int chs, struct nid_path *path)
+{
+ unsigned int val;
+ if (!path)
+ return 0;
+ val = path->ctls[NID_PATH_VOL_CTL];
+ if (!val)
+ return 0;
+ val = amp_val_replace_channels(val, chs);
+ return __add_pb_vol_ctrl(codec->spec, HDA_CTL_WIDGET_VOL, pfx, cidx, val);
+}
+
+/* return the channel bits suitable for the given path->ctls[] */
+static int get_default_ch_nums(struct hda_codec *codec, struct nid_path *path,
+ int type)
+{
+ int chs = 1; /* mono (left only) */
+ if (path) {
+ hda_nid_t nid = get_amp_nid_(path->ctls[type]);
+ if (nid && (get_wcaps(codec, nid) & AC_WCAP_STEREO))
+ chs = 3; /* stereo */
+ }
+ return chs;
+}
+
+static int add_stereo_vol(struct hda_codec *codec, const char *pfx, int cidx,
+ struct nid_path *path)
+{
+ int chs = get_default_ch_nums(codec, path, NID_PATH_VOL_CTL);
+ return add_vol_ctl(codec, pfx, cidx, chs, path);
+}
+
+/* create a mute-switch for the given mixer widget;
+ * if it has multiple sources (e.g. DAC and loopback), create a bind-mute
+ */
+static int add_sw_ctl(struct hda_codec *codec, const char *pfx, int cidx,
+ unsigned int chs, struct nid_path *path)
+{
+ unsigned int val;
+ int type = HDA_CTL_WIDGET_MUTE;
+
+ if (!path)
+ return 0;
+ val = path->ctls[NID_PATH_MUTE_CTL];
+ if (!val)
+ return 0;
+ val = amp_val_replace_channels(val, chs);
+ if (get_amp_direction_(val) == HDA_INPUT) {
+ hda_nid_t nid = get_amp_nid_(val);
+ int nums = snd_hda_get_num_conns(codec, nid);
+ if (nums > 1) {
+ type = HDA_CTL_BIND_MUTE;
+ val |= nums << 19;
+ }
+ }
+ return __add_pb_sw_ctrl(codec->spec, type, pfx, cidx, val);
+}
+
+static int add_stereo_sw(struct hda_codec *codec, const char *pfx,
+ int cidx, struct nid_path *path)
+{
+ int chs = get_default_ch_nums(codec, path, NID_PATH_MUTE_CTL);
+ return add_sw_ctl(codec, pfx, cidx, chs, path);
+}
+
+/* playback mute control with the software mute bit check */
+static void sync_auto_mute_bits(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->auto_mute_via_amp) {
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ bool enabled = !((spec->mute_bits >> nid) & 1);
+ ucontrol->value.integer.value[0] &= enabled;
+ ucontrol->value.integer.value[1] &= enabled;
+ }
+}
+
+static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ sync_auto_mute_bits(kcontrol, ucontrol);
+ return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+}
+
+/*
+ * Bound mute controls
+ */
+#define AMP_VAL_IDX_SHIFT 19
+#define AMP_VAL_IDX_MASK (0x0f<<19)
+
+static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned long pval;
+ int err;
+
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */
+ err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ return err;
+}
+
+static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned long pval;
+ int i, indices, err = 0, change = 0;
+
+ sync_auto_mute_bits(kcontrol, ucontrol);
+
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT;
+ for (i = 0; i < indices; i++) {
+ kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) |
+ (i << AMP_VAL_IDX_SHIFT);
+ err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ if (err < 0)
+ break;
+ change |= err;
+ }
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ return err < 0 ? err : change;
+}
+
+/* any ctl assigned to the path with the given index? */
+static bool path_has_mixer(struct hda_codec *codec, int path_idx, int ctl_type)
+{
+ struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx);
+ return path && path->ctls[ctl_type];
+}
+
+static const char * const channel_name[] = {
+ "Front", "Surround", "CLFE", "Side", "Back",
+};
+
+/* give some appropriate ctl name prefix for the given line out channel */
+static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
+ int *index, int ctl_type)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+
+ *index = 0;
+ if (cfg->line_outs == 1 && !spec->multi_ios &&
+ !codec->force_pin_prefix &&
+ !cfg->hp_outs && !cfg->speaker_outs)
+ return spec->vmaster_mute.hook ? "PCM" : "Master";
+
+ /* if there is really a single DAC used in the whole output paths,
+ * use it master (or "PCM" if a vmaster hook is present)
+ */
+ if (spec->multiout.num_dacs == 1 && !spec->mixer_nid &&
+ !codec->force_pin_prefix &&
+ !spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0])
+ return spec->vmaster_mute.hook ? "PCM" : "Master";
+
+ /* multi-io channels */
+ if (ch >= cfg->line_outs)
+ goto fixed_name;
+
+ switch (cfg->line_out_type) {
+ case AUTO_PIN_SPEAKER_OUT:
+ /* if the primary channel vol/mute is shared with HP volume,
+ * don't name it as Speaker
+ */
+ if (!ch && cfg->hp_outs &&
+ !path_has_mixer(codec, spec->hp_paths[0], ctl_type))
+ break;
+ if (cfg->line_outs == 1)
+ return "Speaker";
+ if (cfg->line_outs == 2)
+ return ch ? "Bass Speaker" : "Speaker";
+ break;
+ case AUTO_PIN_HP_OUT:
+ /* if the primary channel vol/mute is shared with spk volume,
+ * don't name it as Headphone
+ */
+ if (!ch && cfg->speaker_outs &&
+ !path_has_mixer(codec, spec->speaker_paths[0], ctl_type))
+ break;
+ /* for multi-io case, only the primary out */
+ if (ch && spec->multi_ios)
+ break;
+ *index = ch;
+ return "Headphone";
+ case AUTO_PIN_LINE_OUT:
+ /* This deals with the case where one HP or one Speaker or
+ * one HP + one Speaker need to share the DAC with LO
+ */
+ if (!ch) {
+ bool hp_lo_shared = false, spk_lo_shared = false;
+
+ if (cfg->speaker_outs)
+ spk_lo_shared = !path_has_mixer(codec,
+ spec->speaker_paths[0], ctl_type);
+ if (cfg->hp_outs)
+ hp_lo_shared = !path_has_mixer(codec, spec->hp_paths[0], ctl_type);
+ if (hp_lo_shared && spk_lo_shared)
+ return spec->vmaster_mute.hook ? "PCM" : "Master";
+ if (hp_lo_shared)
+ return "Headphone+LO";
+ if (spk_lo_shared)
+ return "Speaker+LO";
+ }
+ }
+
+ /* for a single channel output, we don't have to name the channel */
+ if (cfg->line_outs == 1 && !spec->multi_ios)
+ return "Line Out";
+
+ fixed_name:
+ if (ch >= ARRAY_SIZE(channel_name)) {
+ snd_BUG();
+ return "PCM";
+ }
+
+ return channel_name[ch];
+}
+
+/*
+ * Parse output paths
+ */
+
+/* badness definition */
+enum {
+ /* No primary DAC is found for the main output */
+ BAD_NO_PRIMARY_DAC = 0x10000,
+ /* No DAC is found for the extra output */
+ BAD_NO_DAC = 0x4000,
+ /* No possible multi-ios */
+ BAD_MULTI_IO = 0x120,
+ /* No individual DAC for extra output */
+ BAD_NO_EXTRA_DAC = 0x102,
+ /* No individual DAC for extra surrounds */
+ BAD_NO_EXTRA_SURR_DAC = 0x101,
+ /* Primary DAC shared with main surrounds */
+ BAD_SHARED_SURROUND = 0x100,
+ /* No independent HP possible */
+ BAD_NO_INDEP_HP = 0x10,
+ /* Primary DAC shared with main CLFE */
+ BAD_SHARED_CLFE = 0x10,
+ /* Primary DAC shared with extra surrounds */
+ BAD_SHARED_EXTRA_SURROUND = 0x10,
+ /* Volume widget is shared */
+ BAD_SHARED_VOL = 0x10,
+};
+
+/* look for widgets in the given path which are appropriate for
+ * volume and mute controls, and assign the values to ctls[].
+ *
+ * When no appropriate widget is found in the path, the badness value
+ * is incremented depending on the situation. The function returns the
+ * total badness for both volume and mute controls.
+ */
+static int assign_out_path_ctls(struct hda_codec *codec, struct nid_path *path)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t nid;
+ unsigned int val;
+ int badness = 0;
+
+ if (!path)
+ return BAD_SHARED_VOL * 2;
+
+ if (path->ctls[NID_PATH_VOL_CTL] ||
+ path->ctls[NID_PATH_MUTE_CTL])
+ return 0; /* already evaluated */
+
+ nid = look_for_out_vol_nid(codec, path);
+ if (nid) {
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ if (spec->dac_min_mute)
+ val |= HDA_AMP_VAL_MIN_MUTE;
+ if (is_ctl_used(codec, val, NID_PATH_VOL_CTL))
+ badness += BAD_SHARED_VOL;
+ else
+ path->ctls[NID_PATH_VOL_CTL] = val;
+ } else
+ badness += BAD_SHARED_VOL;
+ nid = look_for_out_mute_nid(codec, path);
+ if (nid) {
+ unsigned int wid_type = get_wcaps_type(get_wcaps(codec, nid));
+ if (wid_type == AC_WID_PIN || wid_type == AC_WID_AUD_OUT ||
+ nid_has_mute(codec, nid, HDA_OUTPUT))
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ else
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT);
+ if (is_ctl_used(codec, val, NID_PATH_MUTE_CTL))
+ badness += BAD_SHARED_VOL;
+ else
+ path->ctls[NID_PATH_MUTE_CTL] = val;
+ } else
+ badness += BAD_SHARED_VOL;
+ return badness;
+}
+
+const struct badness_table hda_main_out_badness = {
+ .no_primary_dac = BAD_NO_PRIMARY_DAC,
+ .no_dac = BAD_NO_DAC,
+ .shared_primary = BAD_NO_PRIMARY_DAC,
+ .shared_surr = BAD_SHARED_SURROUND,
+ .shared_clfe = BAD_SHARED_CLFE,
+ .shared_surr_main = BAD_SHARED_SURROUND,
+};
+EXPORT_SYMBOL_GPL(hda_main_out_badness);
+
+const struct badness_table hda_extra_out_badness = {
+ .no_primary_dac = BAD_NO_DAC,
+ .no_dac = BAD_NO_DAC,
+ .shared_primary = BAD_NO_EXTRA_DAC,
+ .shared_surr = BAD_SHARED_EXTRA_SURROUND,
+ .shared_clfe = BAD_SHARED_EXTRA_SURROUND,
+ .shared_surr_main = BAD_NO_EXTRA_SURR_DAC,
+};
+EXPORT_SYMBOL_GPL(hda_extra_out_badness);
+
+/* get the DAC of the primary output corresponding to the given array index */
+static hda_nid_t get_primary_out(struct hda_codec *codec, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+
+ if (cfg->line_outs > idx)
+ return spec->private_dac_nids[idx];
+ idx -= cfg->line_outs;
+ if (spec->multi_ios > idx)
+ return spec->multi_io[idx].dac;
+ return 0;
+}
+
+/* return the DAC if it's reachable, otherwise zero */
+static inline hda_nid_t try_dac(struct hda_codec *codec,
+ hda_nid_t dac, hda_nid_t pin)
+{
+ return is_reachable_path(codec, dac, pin) ? dac : 0;
+}
+
+/* try to assign DACs to pins and return the resultant badness */
+static int try_assign_dacs(struct hda_codec *codec, int num_outs,
+ const hda_nid_t *pins, hda_nid_t *dacs,
+ int *path_idx,
+ const struct badness_table *bad)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i, j;
+ int badness = 0;
+ hda_nid_t dac;
+
+ if (!num_outs)
+ return 0;
+
+ for (i = 0; i < num_outs; i++) {
+ struct nid_path *path;
+ hda_nid_t pin = pins[i];
+
+ if (!spec->obey_preferred_dacs) {
+ path = snd_hda_get_path_from_idx(codec, path_idx[i]);
+ if (path) {
+ badness += assign_out_path_ctls(codec, path);
+ continue;
+ }
+ }
+
+ dacs[i] = get_preferred_dac(codec, pin);
+ if (dacs[i]) {
+ if (is_dac_already_used(codec, dacs[i]))
+ badness += bad->shared_primary;
+ } else if (spec->obey_preferred_dacs) {
+ badness += BAD_NO_PRIMARY_DAC;
+ }
+
+ if (!dacs[i])
+ dacs[i] = look_for_dac(codec, pin, false);
+ if (!dacs[i] && !i) {
+ /* try to steal the DAC of surrounds for the front */
+ for (j = 1; j < num_outs; j++) {
+ if (is_reachable_path(codec, dacs[j], pin)) {
+ dacs[0] = dacs[j];
+ dacs[j] = 0;
+ invalidate_nid_path(codec, path_idx[j]);
+ path_idx[j] = 0;
+ break;
+ }
+ }
+ }
+ dac = dacs[i];
+ if (!dac) {
+ if (num_outs > 2)
+ dac = try_dac(codec, get_primary_out(codec, i), pin);
+ if (!dac)
+ dac = try_dac(codec, dacs[0], pin);
+ if (!dac)
+ dac = try_dac(codec, get_primary_out(codec, i), pin);
+ if (dac) {
+ if (!i)
+ badness += bad->shared_primary;
+ else if (i == 1)
+ badness += bad->shared_surr;
+ else
+ badness += bad->shared_clfe;
+ } else if (is_reachable_path(codec, spec->private_dac_nids[0], pin)) {
+ dac = spec->private_dac_nids[0];
+ badness += bad->shared_surr_main;
+ } else if (!i)
+ badness += bad->no_primary_dac;
+ else
+ badness += bad->no_dac;
+ }
+ if (!dac)
+ continue;
+ path = snd_hda_add_new_path(codec, dac, pin, -spec->mixer_nid);
+ if (!path && !i && spec->mixer_nid) {
+ /* try with aamix */
+ path = snd_hda_add_new_path(codec, dac, pin, 0);
+ }
+ if (!path) {
+ dac = dacs[i] = 0;
+ badness += bad->no_dac;
+ } else {
+ /* print_nid_path(codec, "output", path); */
+ path->active = true;
+ path_idx[i] = snd_hda_get_path_idx(codec, path);
+ badness += assign_out_path_ctls(codec, path);
+ }
+ }
+
+ return badness;
+}
+
+/* return NID if the given pin has only a single connection to a certain DAC */
+static hda_nid_t get_dac_if_single(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+ hda_nid_t nid_found = 0;
+
+ for (i = 0; i < spec->num_all_dacs; i++) {
+ hda_nid_t nid = spec->all_dacs[i];
+ if (!nid || is_dac_already_used(codec, nid))
+ continue;
+ if (is_reachable_path(codec, nid, pin)) {
+ if (nid_found)
+ return 0;
+ nid_found = nid;
+ }
+ }
+ return nid_found;
+}
+
+/* check whether the given pin can be a multi-io pin */
+static bool can_be_multiio_pin(struct hda_codec *codec,
+ unsigned int location, hda_nid_t nid)
+{
+ unsigned int defcfg, caps;
+
+ defcfg = snd_hda_codec_get_pincfg(codec, nid);
+ if (get_defcfg_connect(defcfg) != AC_JACK_PORT_COMPLEX)
+ return false;
+ if (location && get_defcfg_location(defcfg) != location)
+ return false;
+ caps = snd_hda_query_pin_caps(codec, nid);
+ if (!(caps & AC_PINCAP_OUT))
+ return false;
+ return true;
+}
+
+/* count the number of input pins that are capable to be multi-io */
+static int count_multiio_pins(struct hda_codec *codec, hda_nid_t reference_pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin);
+ unsigned int location = get_defcfg_location(defcfg);
+ int type, i;
+ int num_pins = 0;
+
+ for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].type != type)
+ continue;
+ if (can_be_multiio_pin(codec, location,
+ cfg->inputs[i].pin))
+ num_pins++;
+ }
+ }
+ return num_pins;
+}
+
+/*
+ * multi-io helper
+ *
+ * When hardwired is set, try to fill ony hardwired pins, and returns
+ * zero if any pins are filled, non-zero if nothing found.
+ * When hardwired is off, try to fill possible input pins, and returns
+ * the badness value.
+ */
+static int fill_multi_ios(struct hda_codec *codec,
+ hda_nid_t reference_pin,
+ bool hardwired)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int type, i, j, num_pins, old_pins;
+ unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin);
+ unsigned int location = get_defcfg_location(defcfg);
+ int badness = 0;
+ struct nid_path *path;
+
+ old_pins = spec->multi_ios;
+ if (old_pins >= 2)
+ goto end_fill;
+
+ num_pins = count_multiio_pins(codec, reference_pin);
+ if (num_pins < 2)
+ goto end_fill;
+
+ for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t nid = cfg->inputs[i].pin;
+ hda_nid_t dac = 0;
+
+ if (cfg->inputs[i].type != type)
+ continue;
+ if (!can_be_multiio_pin(codec, location, nid))
+ continue;
+ for (j = 0; j < spec->multi_ios; j++) {
+ if (nid == spec->multi_io[j].pin)
+ break;
+ }
+ if (j < spec->multi_ios)
+ continue;
+
+ if (hardwired)
+ dac = get_dac_if_single(codec, nid);
+ else if (!dac)
+ dac = look_for_dac(codec, nid, false);
+ if (!dac) {
+ badness++;
+ continue;
+ }
+ path = snd_hda_add_new_path(codec, dac, nid,
+ -spec->mixer_nid);
+ if (!path) {
+ badness++;
+ continue;
+ }
+ /* print_nid_path(codec, "multiio", path); */
+ spec->multi_io[spec->multi_ios].pin = nid;
+ spec->multi_io[spec->multi_ios].dac = dac;
+ spec->out_paths[cfg->line_outs + spec->multi_ios] =
+ snd_hda_get_path_idx(codec, path);
+ spec->multi_ios++;
+ if (spec->multi_ios >= 2)
+ break;
+ }
+ }
+ end_fill:
+ if (badness)
+ badness = BAD_MULTI_IO;
+ if (old_pins == spec->multi_ios) {
+ if (hardwired)
+ return 1; /* nothing found */
+ else
+ return badness; /* no badness if nothing found */
+ }
+ if (!hardwired && spec->multi_ios < 2) {
+ /* cancel newly assigned paths */
+ spec->paths.used -= spec->multi_ios - old_pins;
+ spec->multi_ios = old_pins;
+ return badness;
+ }
+
+ /* assign volume and mute controls */
+ for (i = old_pins; i < spec->multi_ios; i++) {
+ path = snd_hda_get_path_from_idx(codec, spec->out_paths[cfg->line_outs + i]);
+ badness += assign_out_path_ctls(codec, path);
+ }
+
+ return badness;
+}
+
+/* map DACs for all pins in the list if they are single connections */
+static bool map_singles(struct hda_codec *codec, int outs,
+ const hda_nid_t *pins, hda_nid_t *dacs, int *path_idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+ bool found = false;
+ for (i = 0; i < outs; i++) {
+ struct nid_path *path;
+ hda_nid_t dac;
+ if (dacs[i])
+ continue;
+ dac = get_dac_if_single(codec, pins[i]);
+ if (!dac)
+ continue;
+ path = snd_hda_add_new_path(codec, dac, pins[i],
+ -spec->mixer_nid);
+ if (!path && !i && spec->mixer_nid)
+ path = snd_hda_add_new_path(codec, dac, pins[i], 0);
+ if (path) {
+ dacs[i] = dac;
+ found = true;
+ /* print_nid_path(codec, "output", path); */
+ path->active = true;
+ path_idx[i] = snd_hda_get_path_idx(codec, path);
+ }
+ }
+ return found;
+}
+
+static inline bool has_aamix_out_paths(struct hda_gen_spec *spec)
+{
+ return spec->aamix_out_paths[0] || spec->aamix_out_paths[1] ||
+ spec->aamix_out_paths[2];
+}
+
+/* create a new path including aamix if available, and return its index */
+static int check_aamix_out_path(struct hda_codec *codec, int path_idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+ hda_nid_t path_dac, dac, pin;
+
+ path = snd_hda_get_path_from_idx(codec, path_idx);
+ if (!path || !path->depth ||
+ is_nid_contained(path, spec->mixer_nid))
+ return 0;
+ path_dac = path->path[0];
+ dac = spec->private_dac_nids[0];
+ pin = path->path[path->depth - 1];
+ path = snd_hda_add_new_path(codec, dac, pin, spec->mixer_nid);
+ if (!path) {
+ if (dac != path_dac)
+ dac = path_dac;
+ else if (spec->multiout.hp_out_nid[0])
+ dac = spec->multiout.hp_out_nid[0];
+ else if (spec->multiout.extra_out_nid[0])
+ dac = spec->multiout.extra_out_nid[0];
+ else
+ dac = 0;
+ if (dac)
+ path = snd_hda_add_new_path(codec, dac, pin,
+ spec->mixer_nid);
+ }
+ if (!path)
+ return 0;
+ /* print_nid_path(codec, "output-aamix", path); */
+ path->active = false; /* unused as default */
+ path->pin_fixed = true; /* static route */
+ return snd_hda_get_path_idx(codec, path);
+}
+
+/* check whether the independent HP is available with the current config */
+static bool indep_hp_possible(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ struct nid_path *path;
+ int i, idx;
+
+ if (cfg->line_out_type == AUTO_PIN_HP_OUT)
+ idx = spec->out_paths[0];
+ else
+ idx = spec->hp_paths[0];
+ path = snd_hda_get_path_from_idx(codec, idx);
+ if (!path)
+ return false;
+
+ /* assume no path conflicts unless aamix is involved */
+ if (!spec->mixer_nid || !is_nid_contained(path, spec->mixer_nid))
+ return true;
+
+ /* check whether output paths contain aamix */
+ for (i = 0; i < cfg->line_outs; i++) {
+ if (spec->out_paths[i] == idx)
+ break;
+ path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]);
+ if (path && is_nid_contained(path, spec->mixer_nid))
+ return false;
+ }
+ for (i = 0; i < cfg->speaker_outs; i++) {
+ path = snd_hda_get_path_from_idx(codec, spec->speaker_paths[i]);
+ if (path && is_nid_contained(path, spec->mixer_nid))
+ return false;
+ }
+
+ return true;
+}
+
+/* fill the empty entries in the dac array for speaker/hp with the
+ * shared dac pointed by the paths
+ */
+static void refill_shared_dacs(struct hda_codec *codec, int num_outs,
+ hda_nid_t *dacs, int *path_idx)
+{
+ struct nid_path *path;
+ int i;
+
+ for (i = 0; i < num_outs; i++) {
+ if (dacs[i])
+ continue;
+ path = snd_hda_get_path_from_idx(codec, path_idx[i]);
+ if (!path)
+ continue;
+ dacs[i] = path->path[0];
+ }
+}
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int fill_and_eval_dacs(struct hda_codec *codec,
+ bool fill_hardwired,
+ bool fill_mio_first)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i, err, badness;
+
+ /* set num_dacs once to full for look_for_dac() */
+ spec->multiout.num_dacs = cfg->line_outs;
+ spec->multiout.dac_nids = spec->private_dac_nids;
+ memset(spec->private_dac_nids, 0, sizeof(spec->private_dac_nids));
+ memset(spec->multiout.hp_out_nid, 0, sizeof(spec->multiout.hp_out_nid));
+ memset(spec->multiout.extra_out_nid, 0, sizeof(spec->multiout.extra_out_nid));
+ spec->multi_ios = 0;
+ snd_array_free(&spec->paths);
+
+ /* clear path indices */
+ memset(spec->out_paths, 0, sizeof(spec->out_paths));
+ memset(spec->hp_paths, 0, sizeof(spec->hp_paths));
+ memset(spec->speaker_paths, 0, sizeof(spec->speaker_paths));
+ memset(spec->aamix_out_paths, 0, sizeof(spec->aamix_out_paths));
+ memset(spec->digout_paths, 0, sizeof(spec->digout_paths));
+ memset(spec->input_paths, 0, sizeof(spec->input_paths));
+ memset(spec->loopback_paths, 0, sizeof(spec->loopback_paths));
+ memset(&spec->digin_path, 0, sizeof(spec->digin_path));
+
+ badness = 0;
+
+ /* fill hard-wired DACs first */
+ if (fill_hardwired) {
+ bool mapped;
+ do {
+ mapped = map_singles(codec, cfg->line_outs,
+ cfg->line_out_pins,
+ spec->private_dac_nids,
+ spec->out_paths);
+ mapped |= map_singles(codec, cfg->hp_outs,
+ cfg->hp_pins,
+ spec->multiout.hp_out_nid,
+ spec->hp_paths);
+ mapped |= map_singles(codec, cfg->speaker_outs,
+ cfg->speaker_pins,
+ spec->multiout.extra_out_nid,
+ spec->speaker_paths);
+ if (!spec->no_multi_io &&
+ fill_mio_first && cfg->line_outs == 1 &&
+ cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ err = fill_multi_ios(codec, cfg->line_out_pins[0], true);
+ if (!err)
+ mapped = true;
+ }
+ } while (mapped);
+ }
+
+ badness += try_assign_dacs(codec, cfg->line_outs, cfg->line_out_pins,
+ spec->private_dac_nids, spec->out_paths,
+ spec->main_out_badness);
+
+ if (!spec->no_multi_io && fill_mio_first &&
+ cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ /* try to fill multi-io first */
+ err = fill_multi_ios(codec, cfg->line_out_pins[0], false);
+ if (err < 0)
+ return err;
+ /* we don't count badness at this stage yet */
+ }
+
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+ err = try_assign_dacs(codec, cfg->hp_outs, cfg->hp_pins,
+ spec->multiout.hp_out_nid,
+ spec->hp_paths,
+ spec->extra_out_badness);
+ if (err < 0)
+ return err;
+ badness += err;
+ }
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ err = try_assign_dacs(codec, cfg->speaker_outs,
+ cfg->speaker_pins,
+ spec->multiout.extra_out_nid,
+ spec->speaker_paths,
+ spec->extra_out_badness);
+ if (err < 0)
+ return err;
+ badness += err;
+ }
+ if (!spec->no_multi_io &&
+ cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ err = fill_multi_ios(codec, cfg->line_out_pins[0], false);
+ if (err < 0)
+ return err;
+ badness += err;
+ }
+
+ if (spec->mixer_nid) {
+ spec->aamix_out_paths[0] =
+ check_aamix_out_path(codec, spec->out_paths[0]);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ spec->aamix_out_paths[1] =
+ check_aamix_out_path(codec, spec->hp_paths[0]);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ spec->aamix_out_paths[2] =
+ check_aamix_out_path(codec, spec->speaker_paths[0]);
+ }
+
+ if (!spec->no_multi_io &&
+ cfg->hp_outs && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+ if (count_multiio_pins(codec, cfg->hp_pins[0]) >= 2)
+ spec->multi_ios = 1; /* give badness */
+
+ /* re-count num_dacs and squash invalid entries */
+ spec->multiout.num_dacs = 0;
+ for (i = 0; i < cfg->line_outs; i++) {
+ if (spec->private_dac_nids[i])
+ spec->multiout.num_dacs++;
+ else {
+ memmove(spec->private_dac_nids + i,
+ spec->private_dac_nids + i + 1,
+ sizeof(hda_nid_t) * (cfg->line_outs - i - 1));
+ spec->private_dac_nids[cfg->line_outs - 1] = 0;
+ }
+ }
+
+ spec->ext_channel_count = spec->min_channel_count =
+ spec->multiout.num_dacs * 2;
+
+ if (spec->multi_ios == 2) {
+ for (i = 0; i < 2; i++)
+ spec->private_dac_nids[spec->multiout.num_dacs++] =
+ spec->multi_io[i].dac;
+ } else if (spec->multi_ios) {
+ spec->multi_ios = 0;
+ badness += BAD_MULTI_IO;
+ }
+
+ if (spec->indep_hp && !indep_hp_possible(codec))
+ badness += BAD_NO_INDEP_HP;
+
+ /* re-fill the shared DAC for speaker / headphone */
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ refill_shared_dacs(codec, cfg->hp_outs,
+ spec->multiout.hp_out_nid,
+ spec->hp_paths);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ refill_shared_dacs(codec, cfg->speaker_outs,
+ spec->multiout.extra_out_nid,
+ spec->speaker_paths);
+
+ return badness;
+}
+
+#define DEBUG_BADNESS
+
+#ifdef DEBUG_BADNESS
+#define debug_badness(fmt, ...) \
+ codec_dbg(codec, fmt, ##__VA_ARGS__)
+#else
+#define debug_badness(fmt, ...) \
+ do { if (0) codec_dbg(codec, fmt, ##__VA_ARGS__); } while (0)
+#endif
+
+#ifdef DEBUG_BADNESS
+static inline void print_nid_path_idx(struct hda_codec *codec,
+ const char *pfx, int idx)
+{
+ struct nid_path *path;
+
+ path = snd_hda_get_path_from_idx(codec, idx);
+ if (path)
+ print_nid_path(codec, pfx, path);
+}
+
+static void debug_show_configs(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ static const char * const lo_type[3] = { "LO", "SP", "HP" };
+ int i;
+
+ debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x (type %s)\n",
+ cfg->line_out_pins[0], cfg->line_out_pins[1],
+ cfg->line_out_pins[2], cfg->line_out_pins[3],
+ spec->multiout.dac_nids[0],
+ spec->multiout.dac_nids[1],
+ spec->multiout.dac_nids[2],
+ spec->multiout.dac_nids[3],
+ lo_type[cfg->line_out_type]);
+ for (i = 0; i < cfg->line_outs; i++)
+ print_nid_path_idx(codec, " out", spec->out_paths[i]);
+ if (spec->multi_ios > 0)
+ debug_badness("multi_ios(%d) = %x/%x : %x/%x\n",
+ spec->multi_ios,
+ spec->multi_io[0].pin, spec->multi_io[1].pin,
+ spec->multi_io[0].dac, spec->multi_io[1].dac);
+ for (i = 0; i < spec->multi_ios; i++)
+ print_nid_path_idx(codec, " mio",
+ spec->out_paths[cfg->line_outs + i]);
+ if (cfg->hp_outs)
+ debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+ cfg->hp_pins[0], cfg->hp_pins[1],
+ cfg->hp_pins[2], cfg->hp_pins[3],
+ spec->multiout.hp_out_nid[0],
+ spec->multiout.hp_out_nid[1],
+ spec->multiout.hp_out_nid[2],
+ spec->multiout.hp_out_nid[3]);
+ for (i = 0; i < cfg->hp_outs; i++)
+ print_nid_path_idx(codec, " hp ", spec->hp_paths[i]);
+ if (cfg->speaker_outs)
+ debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+ cfg->speaker_pins[0], cfg->speaker_pins[1],
+ cfg->speaker_pins[2], cfg->speaker_pins[3],
+ spec->multiout.extra_out_nid[0],
+ spec->multiout.extra_out_nid[1],
+ spec->multiout.extra_out_nid[2],
+ spec->multiout.extra_out_nid[3]);
+ for (i = 0; i < cfg->speaker_outs; i++)
+ print_nid_path_idx(codec, " spk", spec->speaker_paths[i]);
+ for (i = 0; i < 3; i++)
+ print_nid_path_idx(codec, " mix", spec->aamix_out_paths[i]);
+}
+#else
+#define debug_show_configs(codec, cfg) /* NOP */
+#endif
+
+/* find all available DACs of the codec */
+static void fill_all_dac_nids(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t nid;
+
+ spec->num_all_dacs = 0;
+ memset(spec->all_dacs, 0, sizeof(spec->all_dacs));
+ for_each_hda_codec_node(nid, codec) {
+ if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_OUT)
+ continue;
+ if (spec->num_all_dacs >= ARRAY_SIZE(spec->all_dacs)) {
+ codec_err(codec, "Too many DACs!\n");
+ break;
+ }
+ spec->all_dacs[spec->num_all_dacs++] = nid;
+ }
+}
+
+static int parse_output_paths(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ struct auto_pin_cfg *best_cfg;
+ unsigned int val;
+ int best_badness = INT_MAX;
+ int badness;
+ bool fill_hardwired = true, fill_mio_first = true;
+ bool best_wired = true, best_mio = true;
+ bool hp_spk_swapped = false;
+
+ best_cfg = kmalloc(sizeof(*best_cfg), GFP_KERNEL);
+ if (!best_cfg)
+ return -ENOMEM;
+ *best_cfg = *cfg;
+
+ for (;;) {
+ badness = fill_and_eval_dacs(codec, fill_hardwired,
+ fill_mio_first);
+ if (badness < 0) {
+ kfree(best_cfg);
+ return badness;
+ }
+ debug_badness("==> lo_type=%d, wired=%d, mio=%d, badness=0x%x\n",
+ cfg->line_out_type, fill_hardwired, fill_mio_first,
+ badness);
+ debug_show_configs(codec, cfg);
+ if (badness < best_badness) {
+ best_badness = badness;
+ *best_cfg = *cfg;
+ best_wired = fill_hardwired;
+ best_mio = fill_mio_first;
+ }
+ if (!badness)
+ break;
+ fill_mio_first = !fill_mio_first;
+ if (!fill_mio_first)
+ continue;
+ fill_hardwired = !fill_hardwired;
+ if (!fill_hardwired)
+ continue;
+ if (hp_spk_swapped)
+ break;
+ hp_spk_swapped = true;
+ if (cfg->speaker_outs > 0 &&
+ cfg->line_out_type == AUTO_PIN_HP_OUT) {
+ cfg->hp_outs = cfg->line_outs;
+ memcpy(cfg->hp_pins, cfg->line_out_pins,
+ sizeof(cfg->hp_pins));
+ cfg->line_outs = cfg->speaker_outs;
+ memcpy(cfg->line_out_pins, cfg->speaker_pins,
+ sizeof(cfg->speaker_pins));
+ cfg->speaker_outs = 0;
+ memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins));
+ cfg->line_out_type = AUTO_PIN_SPEAKER_OUT;
+ fill_hardwired = true;
+ continue;
+ }
+ if (cfg->hp_outs > 0 &&
+ cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
+ cfg->speaker_outs = cfg->line_outs;
+ memcpy(cfg->speaker_pins, cfg->line_out_pins,
+ sizeof(cfg->speaker_pins));
+ cfg->line_outs = cfg->hp_outs;
+ memcpy(cfg->line_out_pins, cfg->hp_pins,
+ sizeof(cfg->hp_pins));
+ cfg->hp_outs = 0;
+ memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
+ cfg->line_out_type = AUTO_PIN_HP_OUT;
+ fill_hardwired = true;
+ continue;
+ }
+ break;
+ }
+
+ if (badness) {
+ debug_badness("==> restoring best_cfg\n");
+ *cfg = *best_cfg;
+ fill_and_eval_dacs(codec, best_wired, best_mio);
+ }
+ debug_badness("==> Best config: lo_type=%d, wired=%d, mio=%d\n",
+ cfg->line_out_type, best_wired, best_mio);
+ debug_show_configs(codec, cfg);
+
+ if (cfg->line_out_pins[0]) {
+ struct nid_path *path;
+ path = snd_hda_get_path_from_idx(codec, spec->out_paths[0]);
+ if (path)
+ spec->vmaster_nid = look_for_out_vol_nid(codec, path);
+ if (spec->vmaster_nid) {
+ snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
+ HDA_OUTPUT, spec->vmaster_tlv);
+ if (spec->dac_min_mute)
+ spec->vmaster_tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] |= TLV_DB_SCALE_MUTE;
+ }
+ }
+
+ /* set initial pinctl targets */
+ if (spec->prefer_hp_amp || cfg->line_out_type == AUTO_PIN_HP_OUT)
+ val = PIN_HP;
+ else
+ val = PIN_OUT;
+ set_pin_targets(codec, cfg->line_outs, cfg->line_out_pins, val);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ set_pin_targets(codec, cfg->hp_outs, cfg->hp_pins, PIN_HP);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ val = spec->prefer_hp_amp ? PIN_HP : PIN_OUT;
+ set_pin_targets(codec, cfg->speaker_outs,
+ cfg->speaker_pins, val);
+ }
+
+ /* clear indep_hp flag if not available */
+ if (spec->indep_hp && !indep_hp_possible(codec))
+ spec->indep_hp = 0;
+
+ kfree(best_cfg);
+ return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int create_multi_out_ctls(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i, err, noutputs;
+
+ noutputs = cfg->line_outs;
+ if (spec->multi_ios > 0 && cfg->line_outs < 3)
+ noutputs += spec->multi_ios;
+
+ for (i = 0; i < noutputs; i++) {
+ const char *name;
+ int index;
+ struct nid_path *path;
+
+ path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]);
+ if (!path)
+ continue;
+
+ name = get_line_out_pfx(codec, i, &index, NID_PATH_VOL_CTL);
+ if (!name || !strcmp(name, "CLFE")) {
+ /* Center/LFE */
+ err = add_vol_ctl(codec, "Center", 0, 1, path);
+ if (err < 0)
+ return err;
+ err = add_vol_ctl(codec, "LFE", 0, 2, path);
+ if (err < 0)
+ return err;
+ } else {
+ err = add_stereo_vol(codec, name, index, path);
+ if (err < 0)
+ return err;
+ }
+
+ name = get_line_out_pfx(codec, i, &index, NID_PATH_MUTE_CTL);
+ if (!name || !strcmp(name, "CLFE")) {
+ err = add_sw_ctl(codec, "Center", 0, 1, path);
+ if (err < 0)
+ return err;
+ err = add_sw_ctl(codec, "LFE", 0, 2, path);
+ if (err < 0)
+ return err;
+ } else {
+ err = add_stereo_sw(codec, name, index, path);
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int create_extra_out(struct hda_codec *codec, int path_idx,
+ const char *pfx, int cidx)
+{
+ struct nid_path *path;
+ int err;
+
+ path = snd_hda_get_path_from_idx(codec, path_idx);
+ if (!path)
+ return 0;
+ err = add_stereo_vol(codec, pfx, cidx, path);
+ if (err < 0)
+ return err;
+ err = add_stereo_sw(codec, pfx, cidx, path);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/* add playback controls for speaker and HP outputs */
+static int create_extra_outs(struct hda_codec *codec, int num_pins,
+ const int *paths, const char *pfx)
+{
+ int i;
+
+ for (i = 0; i < num_pins; i++) {
+ const char *name;
+ char tmp[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int err, idx = 0;
+
+ if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker"))
+ name = "Bass Speaker";
+ else if (num_pins >= 3) {
+ snprintf(tmp, sizeof(tmp), "%s %s",
+ pfx, channel_name[i]);
+ name = tmp;
+ } else {
+ name = pfx;
+ idx = i;
+ }
+ err = create_extra_out(codec, paths[i], name, idx);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int create_hp_out_ctls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return create_extra_outs(codec, spec->autocfg.hp_outs,
+ spec->hp_paths,
+ "Headphone");
+}
+
+static int create_speaker_out_ctls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return create_extra_outs(codec, spec->autocfg.speaker_outs,
+ spec->speaker_paths,
+ "Speaker");
+}
+
+/*
+ * independent HP controls
+ */
+
+static void call_hp_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack);
+static int indep_hp_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
+}
+
+static int indep_hp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ ucontrol->value.enumerated.item[0] = spec->indep_hp_enabled;
+ return 0;
+}
+
+static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
+ int nomix_path_idx, int mix_path_idx,
+ int out_type);
+
+static int indep_hp_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ unsigned int select = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ mutex_lock(&spec->pcm_mutex);
+ if (spec->active_streams) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (spec->indep_hp_enabled != select) {
+ hda_nid_t *dacp;
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ dacp = &spec->private_dac_nids[0];
+ else
+ dacp = &spec->multiout.hp_out_nid[0];
+
+ /* update HP aamix paths in case it conflicts with indep HP */
+ if (spec->have_aamix_ctl) {
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ update_aamix_paths(codec, spec->aamix_mode,
+ spec->out_paths[0],
+ spec->aamix_out_paths[0],
+ spec->autocfg.line_out_type);
+ else
+ update_aamix_paths(codec, spec->aamix_mode,
+ spec->hp_paths[0],
+ spec->aamix_out_paths[1],
+ AUTO_PIN_HP_OUT);
+ }
+
+ spec->indep_hp_enabled = select;
+ if (spec->indep_hp_enabled)
+ *dacp = 0;
+ else
+ *dacp = spec->alt_dac_nid;
+
+ call_hp_automute(codec, NULL);
+ ret = 1;
+ }
+ unlock:
+ mutex_unlock(&spec->pcm_mutex);
+ return ret;
+}
+
+static const struct snd_kcontrol_new indep_hp_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Independent HP",
+ .info = indep_hp_info,
+ .get = indep_hp_get,
+ .put = indep_hp_put,
+};
+
+
+static int create_indep_hp_ctls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t dac;
+
+ if (!spec->indep_hp)
+ return 0;
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ dac = spec->multiout.dac_nids[0];
+ else
+ dac = spec->multiout.hp_out_nid[0];
+ if (!dac) {
+ spec->indep_hp = 0;
+ return 0;
+ }
+
+ spec->indep_hp_enabled = false;
+ spec->alt_dac_nid = dac;
+ if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl))
+ return -ENOMEM;
+ return 0;
+}
+
+/*
+ * channel mode enum control
+ */
+
+static int ch_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ int chs;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = spec->multi_ios + 1;
+ if (uinfo->value.enumerated.item > spec->multi_ios)
+ uinfo->value.enumerated.item = spec->multi_ios;
+ chs = uinfo->value.enumerated.item * 2 + spec->min_channel_count;
+ sprintf(uinfo->value.enumerated.name, "%dch", chs);
+ return 0;
+}
+
+static int ch_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ ucontrol->value.enumerated.item[0] =
+ (spec->ext_channel_count - spec->min_channel_count) / 2;
+ return 0;
+}
+
+static inline struct nid_path *
+get_multiio_path(struct hda_codec *codec, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_get_path_from_idx(codec,
+ spec->out_paths[spec->autocfg.line_outs + idx]);
+}
+
+static void update_automute_all(struct hda_codec *codec);
+
+/* Default value to be passed as aamix argument for snd_hda_activate_path();
+ * used for output paths
+ */
+static bool aamix_default(struct hda_gen_spec *spec)
+{
+ return !spec->have_aamix_ctl || spec->aamix_mode;
+}
+
+static int set_multi_io(struct hda_codec *codec, int idx, bool output)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t nid = spec->multi_io[idx].pin;
+ struct nid_path *path;
+
+ path = get_multiio_path(codec, idx);
+ if (!path)
+ return -EINVAL;
+
+ if (path->active == output)
+ return 0;
+
+ if (output) {
+ set_pin_target(codec, nid, PIN_OUT, true);
+ snd_hda_activate_path(codec, path, true, aamix_default(spec));
+ set_pin_eapd(codec, nid, true);
+ } else {
+ set_pin_eapd(codec, nid, false);
+ snd_hda_activate_path(codec, path, false, aamix_default(spec));
+ set_pin_target(codec, nid, spec->multi_io[idx].ctl_in, true);
+ path_power_down_sync(codec, path);
+ }
+
+ /* update jack retasking in case it modifies any of them */
+ update_automute_all(codec);
+
+ return 0;
+}
+
+static int ch_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ int i, ch;
+
+ ch = ucontrol->value.enumerated.item[0];
+ if (ch < 0 || ch > spec->multi_ios)
+ return -EINVAL;
+ if (ch == (spec->ext_channel_count - spec->min_channel_count) / 2)
+ return 0;
+ spec->ext_channel_count = ch * 2 + spec->min_channel_count;
+ for (i = 0; i < spec->multi_ios; i++)
+ set_multi_io(codec, i, i < ch);
+ spec->multiout.max_channels = max(spec->ext_channel_count,
+ spec->const_channel_count);
+ if (spec->need_dac_fix)
+ spec->multiout.num_dacs = spec->multiout.max_channels / 2;
+ return 1;
+}
+
+static const struct snd_kcontrol_new channel_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Channel Mode",
+ .info = ch_mode_info,
+ .get = ch_mode_get,
+ .put = ch_mode_put,
+};
+
+static int create_multi_channel_mode(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->multi_ios > 0) {
+ if (!snd_hda_gen_add_kctl(spec, NULL, &channel_mode_enum))
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/*
+ * aamix loopback enable/disable switch
+ */
+
+#define loopback_mixing_info indep_hp_info
+
+static int loopback_mixing_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ ucontrol->value.enumerated.item[0] = spec->aamix_mode;
+ return 0;
+}
+
+static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
+ int nomix_path_idx, int mix_path_idx,
+ int out_type)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *nomix_path, *mix_path;
+
+ nomix_path = snd_hda_get_path_from_idx(codec, nomix_path_idx);
+ mix_path = snd_hda_get_path_from_idx(codec, mix_path_idx);
+ if (!nomix_path || !mix_path)
+ return;
+
+ /* if HP aamix path is driven from a different DAC and the
+ * independent HP mode is ON, can't turn on aamix path
+ */
+ if (out_type == AUTO_PIN_HP_OUT && spec->indep_hp_enabled &&
+ mix_path->path[0] != spec->alt_dac_nid)
+ do_mix = false;
+
+ if (do_mix) {
+ snd_hda_activate_path(codec, nomix_path, false, true);
+ snd_hda_activate_path(codec, mix_path, true, true);
+ path_power_down_sync(codec, nomix_path);
+ } else {
+ snd_hda_activate_path(codec, mix_path, false, false);
+ snd_hda_activate_path(codec, nomix_path, true, false);
+ path_power_down_sync(codec, mix_path);
+ }
+}
+
+/* re-initialize the output paths; only called from loopback_mixing_put() */
+static void update_output_paths(struct hda_codec *codec, int num_outs,
+ const int *paths)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+ int i;
+
+ for (i = 0; i < num_outs; i++) {
+ path = snd_hda_get_path_from_idx(codec, paths[i]);
+ if (path)
+ snd_hda_activate_path(codec, path, path->active,
+ spec->aamix_mode);
+ }
+}
+
+static int loopback_mixing_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ const struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int val = ucontrol->value.enumerated.item[0];
+
+ if (val == spec->aamix_mode)
+ return 0;
+ spec->aamix_mode = val;
+ if (has_aamix_out_paths(spec)) {
+ update_aamix_paths(codec, val, spec->out_paths[0],
+ spec->aamix_out_paths[0],
+ cfg->line_out_type);
+ update_aamix_paths(codec, val, spec->hp_paths[0],
+ spec->aamix_out_paths[1],
+ AUTO_PIN_HP_OUT);
+ update_aamix_paths(codec, val, spec->speaker_paths[0],
+ spec->aamix_out_paths[2],
+ AUTO_PIN_SPEAKER_OUT);
+ } else {
+ update_output_paths(codec, cfg->line_outs, spec->out_paths);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ update_output_paths(codec, cfg->hp_outs, spec->hp_paths);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ update_output_paths(codec, cfg->speaker_outs,
+ spec->speaker_paths);
+ }
+ return 1;
+}
+
+static const struct snd_kcontrol_new loopback_mixing_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Loopback Mixing",
+ .info = loopback_mixing_info,
+ .get = loopback_mixing_get,
+ .put = loopback_mixing_put,
+};
+
+static int create_loopback_mixing_ctl(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!spec->mixer_nid)
+ return 0;
+ if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum))
+ return -ENOMEM;
+ spec->have_aamix_ctl = 1;
+ return 0;
+}
+
+/*
+ * shared headphone/mic handling
+ */
+
+static void call_update_outputs(struct hda_codec *codec);
+
+/* for shared I/O, change the pin-control accordingly */
+static void update_hp_mic(struct hda_codec *codec, int adc_mux, bool force)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ bool as_mic;
+ unsigned int val;
+ hda_nid_t pin;
+
+ pin = spec->hp_mic_pin;
+ as_mic = spec->cur_mux[adc_mux] == spec->hp_mic_mux_idx;
+
+ if (!force) {
+ val = snd_hda_codec_get_pin_target(codec, pin);
+ if (as_mic) {
+ if (val & PIN_IN)
+ return;
+ } else {
+ if (val & PIN_OUT)
+ return;
+ }
+ }
+
+ val = snd_hda_get_default_vref(codec, pin);
+ /* if the HP pin doesn't support VREF and the codec driver gives an
+ * alternative pin, set up the VREF on that pin instead
+ */
+ if (val == AC_PINCTL_VREF_HIZ && spec->shared_mic_vref_pin) {
+ const hda_nid_t vref_pin = spec->shared_mic_vref_pin;
+ unsigned int vref_val = snd_hda_get_default_vref(codec, vref_pin);
+ if (vref_val != AC_PINCTL_VREF_HIZ)
+ snd_hda_set_pin_ctl_cache(codec, vref_pin,
+ PIN_IN | (as_mic ? vref_val : 0));
+ }
+
+ if (!spec->hp_mic_jack_modes) {
+ if (as_mic)
+ val |= PIN_IN;
+ else
+ val = PIN_HP;
+ set_pin_target(codec, pin, val, true);
+ call_hp_automute(codec, NULL);
+ }
+}
+
+/* create a shared input with the headphone out */
+static int create_hp_mic(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int defcfg;
+ hda_nid_t nid;
+
+ if (!spec->hp_mic) {
+ if (spec->suppress_hp_mic_detect)
+ return 0;
+ /* automatic detection: only if no input or a single internal
+ * input pin is found, try to detect the shared hp/mic
+ */
+ if (cfg->num_inputs > 1)
+ return 0;
+ else if (cfg->num_inputs == 1) {
+ defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin);
+ if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT)
+ return 0;
+ }
+ }
+
+ spec->hp_mic = 0; /* clear once */
+ if (cfg->num_inputs >= AUTO_CFG_MAX_INS)
+ return 0;
+
+ nid = 0;
+ if (cfg->line_out_type == AUTO_PIN_HP_OUT && cfg->line_outs > 0)
+ nid = cfg->line_out_pins[0];
+ else if (cfg->hp_outs > 0)
+ nid = cfg->hp_pins[0];
+ if (!nid)
+ return 0;
+
+ if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN))
+ return 0; /* no input */
+
+ cfg->inputs[cfg->num_inputs].pin = nid;
+ cfg->inputs[cfg->num_inputs].type = AUTO_PIN_MIC;
+ cfg->inputs[cfg->num_inputs].is_headphone_mic = 1;
+ cfg->num_inputs++;
+ spec->hp_mic = 1;
+ spec->hp_mic_pin = nid;
+ /* we can't handle auto-mic together with HP-mic */
+ spec->suppress_auto_mic = 1;
+ codec_dbg(codec, "Enable shared I/O jack on NID 0x%x\n", nid);
+ return 0;
+}
+
+/*
+ * output jack mode
+ */
+
+static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin);
+
+static const char * const out_jack_texts[] = {
+ "Line Out", "Headphone Out",
+};
+
+static int out_jack_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_hda_enum_helper_info(kcontrol, uinfo, 2, out_jack_texts);
+}
+
+static int out_jack_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ if (snd_hda_codec_get_pin_target(codec, nid) == PIN_HP)
+ ucontrol->value.enumerated.item[0] = 1;
+ else
+ ucontrol->value.enumerated.item[0] = 0;
+ return 0;
+}
+
+static int out_jack_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val;
+
+ val = ucontrol->value.enumerated.item[0] ? PIN_HP : PIN_OUT;
+ if (snd_hda_codec_get_pin_target(codec, nid) == val)
+ return 0;
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
+ return 1;
+}
+
+static const struct snd_kcontrol_new out_jack_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = out_jack_mode_info,
+ .get = out_jack_mode_get,
+ .put = out_jack_mode_put,
+};
+
+static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct snd_kcontrol_new *kctl;
+ int i;
+
+ snd_array_for_each(&spec->kctls, i, kctl) {
+ if (!strcmp(kctl->name, name) && kctl->index == idx)
+ return true;
+ }
+ return false;
+}
+
+static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin,
+ char *name, size_t name_len)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int idx = 0;
+
+ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx);
+ strlcat(name, " Jack Mode", name_len);
+
+ for (; find_kctl_name(codec, name, idx); idx++)
+ ;
+}
+
+static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->add_jack_modes) {
+ unsigned int pincap = snd_hda_query_pin_caps(codec, pin);
+ if ((pincap & AC_PINCAP_OUT) && (pincap & AC_PINCAP_HP_DRV))
+ return 2;
+ }
+ return 1;
+}
+
+static int create_out_jack_modes(struct hda_codec *codec, int num_pins,
+ hda_nid_t *pins)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < num_pins; i++) {
+ hda_nid_t pin = pins[i];
+ if (pin == spec->hp_mic_pin)
+ continue;
+ if (get_out_jack_num_items(codec, pin) > 1) {
+ struct snd_kcontrol_new *knew;
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ get_jack_mode_name(codec, pin, name, sizeof(name));
+ knew = snd_hda_gen_add_kctl(spec, name,
+ &out_jack_mode_enum);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = pin;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * input jack mode
+ */
+
+/* from AC_PINCTL_VREF_HIZ to AC_PINCTL_VREF_100 */
+#define NUM_VREFS 6
+
+static const char * const vref_texts[NUM_VREFS] = {
+ "Line In", "Mic 50pc Bias", "Mic 0V Bias",
+ "", "Mic 80pc Bias", "Mic 100pc Bias"
+};
+
+static unsigned int get_vref_caps(struct hda_codec *codec, hda_nid_t pin)
+{
+ unsigned int pincap;
+
+ pincap = snd_hda_query_pin_caps(codec, pin);
+ pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ /* filter out unusual vrefs */
+ pincap &= ~(AC_PINCAP_VREF_GRD | AC_PINCAP_VREF_100);
+ return pincap;
+}
+
+/* convert from the enum item index to the vref ctl index (0=HIZ, 1=50%...) */
+static int get_vref_idx(unsigned int vref_caps, unsigned int item_idx)
+{
+ unsigned int i, n = 0;
+
+ for (i = 0; i < NUM_VREFS; i++) {
+ if (vref_caps & (1 << i)) {
+ if (n == item_idx)
+ return i;
+ n++;
+ }
+ }
+ return 0;
+}
+
+/* convert back from the vref ctl index to the enum item index */
+static int cvt_from_vref_idx(unsigned int vref_caps, unsigned int idx)
+{
+ unsigned int i, n = 0;
+
+ for (i = 0; i < NUM_VREFS; i++) {
+ if (i == idx)
+ return n;
+ if (vref_caps & (1 << i))
+ n++;
+ }
+ return 0;
+}
+
+static int in_jack_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+
+ snd_hda_enum_helper_info(kcontrol, uinfo, hweight32(vref_caps),
+ vref_texts);
+ /* set the right text */
+ strcpy(uinfo->value.enumerated.name,
+ vref_texts[get_vref_idx(vref_caps, uinfo->value.enumerated.item)]);
+ return 0;
+}
+
+static int in_jack_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ unsigned int idx;
+
+ idx = snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_VREFEN;
+ ucontrol->value.enumerated.item[0] = cvt_from_vref_idx(vref_caps, idx);
+ return 0;
+}
+
+static int in_jack_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ unsigned int val, idx;
+
+ val = snd_hda_codec_get_pin_target(codec, nid);
+ idx = cvt_from_vref_idx(vref_caps, val & AC_PINCTL_VREFEN);
+ if (idx == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ val &= ~AC_PINCTL_VREFEN;
+ val |= get_vref_idx(vref_caps, ucontrol->value.enumerated.item[0]);
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
+ return 1;
+}
+
+static const struct snd_kcontrol_new in_jack_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = in_jack_mode_info,
+ .get = in_jack_mode_get,
+ .put = in_jack_mode_put,
+};
+
+static int get_in_jack_num_items(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int nitems = 0;
+ if (spec->add_jack_modes)
+ nitems = hweight32(get_vref_caps(codec, pin));
+ return nitems ? nitems : 1;
+}
+
+static int create_in_jack_mode(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct snd_kcontrol_new *knew;
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ unsigned int defcfg;
+
+ if (pin == spec->hp_mic_pin)
+ return 0; /* already done in create_out_jack_mode() */
+
+ /* no jack mode for fixed pins */
+ defcfg = snd_hda_codec_get_pincfg(codec, pin);
+ if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT)
+ return 0;
+
+ /* no multiple vref caps? */
+ if (get_in_jack_num_items(codec, pin) <= 1)
+ return 0;
+
+ get_jack_mode_name(codec, pin, name, sizeof(name));
+ knew = snd_hda_gen_add_kctl(spec, name, &in_jack_mode_enum);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = pin;
+ return 0;
+}
+
+/*
+ * HP/mic shared jack mode
+ */
+static int hp_mic_jack_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ int out_jacks = get_out_jack_num_items(codec, nid);
+ int in_jacks = get_in_jack_num_items(codec, nid);
+ const char *text = NULL;
+ int idx;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = out_jacks + in_jacks;
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+ idx = uinfo->value.enumerated.item;
+ if (idx < out_jacks) {
+ if (out_jacks > 1)
+ text = out_jack_texts[idx];
+ else
+ text = "Headphone Out";
+ } else {
+ idx -= out_jacks;
+ if (in_jacks > 1) {
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ text = vref_texts[get_vref_idx(vref_caps, idx)];
+ } else
+ text = "Mic In";
+ }
+
+ strcpy(uinfo->value.enumerated.name, text);
+ return 0;
+}
+
+static int get_cur_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t nid)
+{
+ int out_jacks = get_out_jack_num_items(codec, nid);
+ int in_jacks = get_in_jack_num_items(codec, nid);
+ unsigned int val = snd_hda_codec_get_pin_target(codec, nid);
+ int idx = 0;
+
+ if (val & PIN_OUT) {
+ if (out_jacks > 1 && val == PIN_HP)
+ idx = 1;
+ } else if (val & PIN_IN) {
+ idx = out_jacks;
+ if (in_jacks > 1) {
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ val &= AC_PINCTL_VREFEN;
+ idx += cvt_from_vref_idx(vref_caps, val);
+ }
+ }
+ return idx;
+}
+
+static int hp_mic_jack_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ ucontrol->value.enumerated.item[0] =
+ get_cur_hp_mic_jack_mode(codec, nid);
+ return 0;
+}
+
+static int hp_mic_jack_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ int out_jacks = get_out_jack_num_items(codec, nid);
+ int in_jacks = get_in_jack_num_items(codec, nid);
+ unsigned int val, oldval, idx;
+
+ oldval = get_cur_hp_mic_jack_mode(codec, nid);
+ idx = ucontrol->value.enumerated.item[0];
+ if (oldval == idx)
+ return 0;
+
+ if (idx < out_jacks) {
+ if (out_jacks > 1)
+ val = idx ? PIN_HP : PIN_OUT;
+ else
+ val = PIN_HP;
+ } else {
+ idx -= out_jacks;
+ if (in_jacks > 1) {
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ val = snd_hda_codec_get_pin_target(codec, nid);
+ val &= ~(AC_PINCTL_VREFEN | PIN_HP);
+ val |= get_vref_idx(vref_caps, idx) | PIN_IN;
+ } else
+ val = snd_hda_get_default_vref(codec, nid) | PIN_IN;
+ }
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
+ call_hp_automute(codec, NULL);
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new hp_mic_jack_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = hp_mic_jack_mode_info,
+ .get = hp_mic_jack_mode_get,
+ .put = hp_mic_jack_mode_put,
+};
+
+static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct snd_kcontrol_new *knew;
+
+ knew = snd_hda_gen_add_kctl(spec, "Headphone Mic Jack Mode",
+ &hp_mic_jack_mode_enum);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = pin;
+ spec->hp_mic_jack_modes = 1;
+ return 0;
+}
+
+/*
+ * Parse input paths
+ */
+
+/* add the powersave loopback-list entry */
+static int add_loopback_list(struct hda_gen_spec *spec, hda_nid_t mix, int idx)
+{
+ struct hda_amp_list *list;
+
+ list = snd_array_new(&spec->loopback_list);
+ if (!list)
+ return -ENOMEM;
+ list->nid = mix;
+ list->dir = HDA_INPUT;
+ list->idx = idx;
+ spec->loopback.amplist = spec->loopback_list.list;
+ return 0;
+}
+
+/* return true if either a volume or a mute amp is found for the given
+ * aamix path; the amp has to be either in the mixer node or its direct leaf
+ */
+static bool look_for_mix_leaf_ctls(struct hda_codec *codec, hda_nid_t mix_nid,
+ hda_nid_t pin, unsigned int *mix_val,
+ unsigned int *mute_val)
+{
+ int idx, num_conns;
+ const hda_nid_t *list;
+ hda_nid_t nid;
+
+ idx = snd_hda_get_conn_index(codec, mix_nid, pin, true);
+ if (idx < 0)
+ return false;
+
+ *mix_val = *mute_val = 0;
+ if (nid_has_volume(codec, mix_nid, HDA_INPUT))
+ *mix_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT);
+ if (nid_has_mute(codec, mix_nid, HDA_INPUT))
+ *mute_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT);
+ if (*mix_val && *mute_val)
+ return true;
+
+ /* check leaf node */
+ num_conns = snd_hda_get_conn_list(codec, mix_nid, &list);
+ if (num_conns < idx)
+ return false;
+ nid = list[idx];
+ if (!*mix_val && nid_has_volume(codec, nid, HDA_OUTPUT) &&
+ !is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_VOL_CTL))
+ *mix_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ if (!*mute_val && nid_has_mute(codec, nid, HDA_OUTPUT) &&
+ !is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_MUTE_CTL))
+ *mute_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+
+ return *mix_val || *mute_val;
+}
+
+/* create input playback/capture controls for the given pin */
+static int new_analog_input(struct hda_codec *codec, int input_idx,
+ hda_nid_t pin, const char *ctlname, int ctlidx,
+ hda_nid_t mix_nid)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+ unsigned int mix_val, mute_val;
+ int err, idx;
+
+ if (!look_for_mix_leaf_ctls(codec, mix_nid, pin, &mix_val, &mute_val))
+ return 0;
+
+ path = snd_hda_add_new_path(codec, pin, mix_nid, 0);
+ if (!path)
+ return -EINVAL;
+ print_nid_path(codec, "loopback", path);
+ spec->loopback_paths[input_idx] = snd_hda_get_path_idx(codec, path);
+
+ idx = path->idx[path->depth - 1];
+ if (mix_val) {
+ err = __add_pb_vol_ctrl(spec, HDA_CTL_WIDGET_VOL, ctlname, ctlidx, mix_val);
+ if (err < 0)
+ return err;
+ path->ctls[NID_PATH_VOL_CTL] = mix_val;
+ }
+
+ if (mute_val) {
+ err = __add_pb_sw_ctrl(spec, HDA_CTL_WIDGET_MUTE, ctlname, ctlidx, mute_val);
+ if (err < 0)
+ return err;
+ path->ctls[NID_PATH_MUTE_CTL] = mute_val;
+ }
+
+ path->active = true;
+ path->stream_enabled = true; /* no DAC/ADC involved */
+ err = add_loopback_list(spec, mix_nid, idx);
+ if (err < 0)
+ return err;
+
+ if (spec->mixer_nid != spec->mixer_merge_nid &&
+ !spec->loopback_merge_path) {
+ path = snd_hda_add_new_path(codec, spec->mixer_nid,
+ spec->mixer_merge_nid, 0);
+ if (path) {
+ print_nid_path(codec, "loopback-merge", path);
+ path->active = true;
+ path->pin_fixed = true; /* static route */
+ path->stream_enabled = true; /* no DAC/ADC involved */
+ spec->loopback_merge_path =
+ snd_hda_get_path_idx(codec, path);
+ }
+ }
+
+ return 0;
+}
+
+static int is_input_pin(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
+ return (pincap & AC_PINCAP_IN) != 0;
+}
+
+/* Parse the codec tree and retrieve ADCs */
+static int fill_adc_nids(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t nid;
+ hda_nid_t *adc_nids = spec->adc_nids;
+ int max_nums = ARRAY_SIZE(spec->adc_nids);
+ int nums = 0;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int caps = get_wcaps(codec, nid);
+ int type = get_wcaps_type(caps);
+
+ if (type != AC_WID_AUD_IN || (caps & AC_WCAP_DIGITAL))
+ continue;
+ adc_nids[nums] = nid;
+ if (++nums >= max_nums)
+ break;
+ }
+ spec->num_adc_nids = nums;
+
+ /* copy the detected ADCs to all_adcs[] */
+ spec->num_all_adcs = nums;
+ memcpy(spec->all_adcs, spec->adc_nids, nums * sizeof(hda_nid_t));
+
+ return nums;
+}
+
+/* filter out invalid adc_nids that don't give all active input pins;
+ * if needed, check whether dynamic ADC-switching is available
+ */
+static int check_dyn_adc_switch(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct hda_input_mux *imux = &spec->input_mux;
+ unsigned int ok_bits;
+ int i, n, nums;
+
+ nums = 0;
+ ok_bits = 0;
+ for (n = 0; n < spec->num_adc_nids; n++) {
+ for (i = 0; i < imux->num_items; i++) {
+ if (!spec->input_paths[i][n])
+ break;
+ }
+ if (i >= imux->num_items) {
+ ok_bits |= (1 << n);
+ nums++;
+ }
+ }
+
+ if (!ok_bits) {
+ /* check whether ADC-switch is possible */
+ for (i = 0; i < imux->num_items; i++) {
+ for (n = 0; n < spec->num_adc_nids; n++) {
+ if (spec->input_paths[i][n]) {
+ spec->dyn_adc_idx[i] = n;
+ break;
+ }
+ }
+ }
+
+ codec_dbg(codec, "enabling ADC switching\n");
+ spec->dyn_adc_switch = 1;
+ } else if (nums != spec->num_adc_nids) {
+ /* shrink the invalid adcs and input paths */
+ nums = 0;
+ for (n = 0; n < spec->num_adc_nids; n++) {
+ if (!(ok_bits & (1 << n)))
+ continue;
+ if (n != nums) {
+ spec->adc_nids[nums] = spec->adc_nids[n];
+ for (i = 0; i < imux->num_items; i++) {
+ invalidate_nid_path(codec,
+ spec->input_paths[i][nums]);
+ spec->input_paths[i][nums] =
+ spec->input_paths[i][n];
+ spec->input_paths[i][n] = 0;
+ }
+ }
+ nums++;
+ }
+ spec->num_adc_nids = nums;
+ }
+
+ if (imux->num_items == 1 ||
+ (imux->num_items == 2 && spec->hp_mic)) {
+ codec_dbg(codec, "reducing to a single ADC\n");
+ spec->num_adc_nids = 1; /* reduce to a single ADC */
+ }
+
+ /* single index for individual volumes ctls */
+ if (!spec->dyn_adc_switch && spec->multi_cap_vol)
+ spec->num_adc_nids = 1;
+
+ return 0;
+}
+
+/* parse capture source paths from the given pin and create imux items */
+static int parse_capture_source(struct hda_codec *codec, hda_nid_t pin,
+ int cfg_idx, int num_adcs,
+ const char *label, int anchor)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct hda_input_mux *imux = &spec->input_mux;
+ int imux_idx = imux->num_items;
+ bool imux_added = false;
+ int c;
+
+ for (c = 0; c < num_adcs; c++) {
+ struct nid_path *path;
+ hda_nid_t adc = spec->adc_nids[c];
+
+ if (!is_reachable_path(codec, pin, adc))
+ continue;
+ path = snd_hda_add_new_path(codec, pin, adc, anchor);
+ if (!path)
+ continue;
+ print_nid_path(codec, "input", path);
+ spec->input_paths[imux_idx][c] =
+ snd_hda_get_path_idx(codec, path);
+
+ if (!imux_added) {
+ if (spec->hp_mic_pin == pin)
+ spec->hp_mic_mux_idx = imux->num_items;
+ spec->imux_pins[imux->num_items] = pin;
+ snd_hda_add_imux_item(codec, imux, label, cfg_idx, NULL);
+ imux_added = true;
+ if (spec->dyn_adc_switch)
+ spec->dyn_adc_idx[imux_idx] = c;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * create playback/capture controls for input pins
+ */
+
+/* fill the label for each input at first */
+static int fill_input_pin_labels(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t pin = cfg->inputs[i].pin;
+ const char *label;
+ int j, idx;
+
+ if (!is_input_pin(codec, pin))
+ continue;
+
+ label = hda_get_autocfg_input_label(codec, cfg, i);
+ idx = 0;
+ for (j = i - 1; j >= 0; j--) {
+ if (spec->input_labels[j] &&
+ !strcmp(spec->input_labels[j], label)) {
+ idx = spec->input_label_idxs[j] + 1;
+ break;
+ }
+ }
+
+ spec->input_labels[i] = label;
+ spec->input_label_idxs[i] = idx;
+ }
+
+ return 0;
+}
+
+#define CFG_IDX_MIX 99 /* a dummy cfg->input idx for stereo mix */
+
+static int create_input_ctls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct auto_pin_cfg *cfg = &spec->autocfg;
+ hda_nid_t mixer = spec->mixer_nid;
+ int num_adcs;
+ int i, err;
+ unsigned int val;
+
+ num_adcs = fill_adc_nids(codec);
+ if (num_adcs < 0)
+ return 0;
+
+ err = fill_input_pin_labels(codec);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t pin;
+
+ pin = cfg->inputs[i].pin;
+ if (!is_input_pin(codec, pin))
+ continue;
+
+ val = PIN_IN;
+ if (cfg->inputs[i].type == AUTO_PIN_MIC)
+ val |= snd_hda_get_default_vref(codec, pin);
+ if (pin != spec->hp_mic_pin &&
+ !snd_hda_codec_get_pin_target(codec, pin))
+ set_pin_target(codec, pin, val, false);
+
+ if (mixer) {
+ if (is_reachable_path(codec, pin, mixer)) {
+ err = new_analog_input(codec, i, pin,
+ spec->input_labels[i],
+ spec->input_label_idxs[i],
+ mixer);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ err = parse_capture_source(codec, pin, i, num_adcs,
+ spec->input_labels[i], -mixer);
+ if (err < 0)
+ return err;
+
+ if (spec->add_jack_modes) {
+ err = create_in_jack_mode(codec, pin);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ /* add stereo mix when explicitly enabled via hint */
+ if (mixer && spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_ENABLE) {
+ err = parse_capture_source(codec, mixer, CFG_IDX_MIX, num_adcs,
+ "Stereo Mix", 0);
+ if (err < 0)
+ return err;
+ else
+ spec->suppress_auto_mic = 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * input source mux
+ */
+
+/* get the input path specified by the given adc and imux indices */
+static struct nid_path *get_input_path(struct hda_codec *codec, int adc_idx, int imux_idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (imux_idx < 0 || imux_idx >= HDA_MAX_NUM_INPUTS) {
+ snd_BUG();
+ return NULL;
+ }
+ if (spec->dyn_adc_switch)
+ adc_idx = spec->dyn_adc_idx[imux_idx];
+ if (adc_idx < 0 || adc_idx >= AUTO_CFG_MAX_INS) {
+ snd_BUG();
+ return NULL;
+ }
+ return snd_hda_get_path_from_idx(codec, spec->input_paths[imux_idx][adc_idx]);
+}
+
+static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
+ unsigned int idx);
+
+static int mux_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_input_mux_info(&spec->input_mux, uinfo);
+}
+
+static int mux_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ /* the ctls are created at once with multiple counts */
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+ return 0;
+}
+
+static int mux_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ return mux_select(codec, adc_idx,
+ ucontrol->value.enumerated.item[0]);
+}
+
+static const struct snd_kcontrol_new cap_src_temp = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Input Source",
+ .info = mux_enum_info,
+ .get = mux_enum_get,
+ .put = mux_enum_put,
+};
+
+/*
+ * capture volume and capture switch ctls
+ */
+
+typedef int (*put_call_t)(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+/* call the given amp update function for all amps in the imux list at once */
+static int cap_put_caller(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ put_call_t func, int type)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ const struct hda_input_mux *imux;
+ struct nid_path *path;
+ int i, adc_idx, ret, err = 0;
+
+ imux = &spec->input_mux;
+ adc_idx = kcontrol->id.index;
+ mutex_lock(&codec->control_mutex);
+ for (i = 0; i < imux->num_items; i++) {
+ path = get_input_path(codec, adc_idx, i);
+ if (!path || !path->ctls[type])
+ continue;
+ kcontrol->private_value = path->ctls[type];
+ ret = func(kcontrol, ucontrol);
+ if (ret < 0) {
+ err = ret;
+ break;
+ }
+ if (ret > 0)
+ err = 1;
+ }
+ mutex_unlock(&codec->control_mutex);
+ if (err >= 0 && spec->cap_sync_hook)
+ spec->cap_sync_hook(codec, kcontrol, ucontrol);
+ return err;
+}
+
+/* capture volume ctl callbacks */
+#define cap_vol_info snd_hda_mixer_amp_volume_info
+#define cap_vol_get snd_hda_mixer_amp_volume_get
+#define cap_vol_tlv snd_hda_mixer_amp_tlv
+
+static int cap_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return cap_put_caller(kcontrol, ucontrol,
+ snd_hda_mixer_amp_volume_put,
+ NID_PATH_VOL_CTL);
+}
+
+static const struct snd_kcontrol_new cap_vol_temp = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Volume",
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK),
+ .info = cap_vol_info,
+ .get = cap_vol_get,
+ .put = cap_vol_put,
+ .tlv = { .c = cap_vol_tlv },
+};
+
+/* capture switch ctl callbacks */
+#define cap_sw_info snd_ctl_boolean_stereo_info
+#define cap_sw_get snd_hda_mixer_amp_switch_get
+
+static int cap_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return cap_put_caller(kcontrol, ucontrol,
+ snd_hda_mixer_amp_switch_put,
+ NID_PATH_MUTE_CTL);
+}
+
+static const struct snd_kcontrol_new cap_sw_temp = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Switch",
+ .info = cap_sw_info,
+ .get = cap_sw_get,
+ .put = cap_sw_put,
+};
+
+static int parse_capvol_in_path(struct hda_codec *codec, struct nid_path *path)
+{
+ hda_nid_t nid;
+ int i, depth;
+
+ path->ctls[NID_PATH_VOL_CTL] = path->ctls[NID_PATH_MUTE_CTL] = 0;
+ for (depth = 0; depth < 3; depth++) {
+ if (depth >= path->depth)
+ return -EINVAL;
+ i = path->depth - depth - 1;
+ nid = path->path[i];
+ if (!path->ctls[NID_PATH_VOL_CTL]) {
+ if (nid_has_volume(codec, nid, HDA_OUTPUT))
+ path->ctls[NID_PATH_VOL_CTL] =
+ HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ else if (nid_has_volume(codec, nid, HDA_INPUT)) {
+ int idx = path->idx[i];
+ if (!depth && codec->single_adc_amp)
+ idx = 0;
+ path->ctls[NID_PATH_VOL_CTL] =
+ HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_INPUT);
+ }
+ }
+ if (!path->ctls[NID_PATH_MUTE_CTL]) {
+ if (nid_has_mute(codec, nid, HDA_OUTPUT))
+ path->ctls[NID_PATH_MUTE_CTL] =
+ HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ else if (nid_has_mute(codec, nid, HDA_INPUT)) {
+ int idx = path->idx[i];
+ if (!depth && codec->single_adc_amp)
+ idx = 0;
+ path->ctls[NID_PATH_MUTE_CTL] =
+ HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_INPUT);
+ }
+ }
+ }
+ return 0;
+}
+
+static bool is_inv_dmic_pin(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int val;
+ int i;
+
+ if (!spec->inv_dmic_split)
+ return false;
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].pin != nid)
+ continue;
+ if (cfg->inputs[i].type != AUTO_PIN_MIC)
+ return false;
+ val = snd_hda_codec_get_pincfg(codec, nid);
+ return snd_hda_get_input_pin_attr(val) == INPUT_PIN_ATTR_INT;
+ }
+ return false;
+}
+
+/* capture switch put callback for a single control with hook call */
+static int cap_single_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ int ret;
+
+ ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ if (spec->cap_sync_hook)
+ spec->cap_sync_hook(codec, kcontrol, ucontrol);
+
+ return ret;
+}
+
+static int add_single_cap_ctl(struct hda_codec *codec, const char *label,
+ int idx, bool is_switch, unsigned int ctl,
+ bool inv_dmic)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ char tmpname[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int type = is_switch ? HDA_CTL_WIDGET_MUTE : HDA_CTL_WIDGET_VOL;
+ const char *sfx = is_switch ? "Switch" : "Volume";
+ unsigned int chs = inv_dmic ? 1 : 3;
+ struct snd_kcontrol_new *knew;
+
+ if (!ctl)
+ return 0;
+
+ if (label)
+ snprintf(tmpname, sizeof(tmpname),
+ "%s Capture %s", label, sfx);
+ else
+ snprintf(tmpname, sizeof(tmpname),
+ "Capture %s", sfx);
+ knew = add_control(spec, type, tmpname, idx,
+ amp_val_replace_channels(ctl, chs));
+ if (!knew)
+ return -ENOMEM;
+ if (is_switch)
+ knew->put = cap_single_sw_put;
+ if (!inv_dmic)
+ return 0;
+
+ /* Make independent right kcontrol */
+ if (label)
+ snprintf(tmpname, sizeof(tmpname),
+ "Inverted %s Capture %s", label, sfx);
+ else
+ snprintf(tmpname, sizeof(tmpname),
+ "Inverted Capture %s", sfx);
+ knew = add_control(spec, type, tmpname, idx,
+ amp_val_replace_channels(ctl, 2));
+ if (!knew)
+ return -ENOMEM;
+ if (is_switch)
+ knew->put = cap_single_sw_put;
+ return 0;
+}
+
+/* create single (and simple) capture volume and switch controls */
+static int create_single_cap_vol_ctl(struct hda_codec *codec, int idx,
+ unsigned int vol_ctl, unsigned int sw_ctl,
+ bool inv_dmic)
+{
+ int err;
+ err = add_single_cap_ctl(codec, NULL, idx, false, vol_ctl, inv_dmic);
+ if (err < 0)
+ return err;
+ err = add_single_cap_ctl(codec, NULL, idx, true, sw_ctl, inv_dmic);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/* create bound capture volume and switch controls */
+static int create_bind_cap_vol_ctl(struct hda_codec *codec, int idx,
+ unsigned int vol_ctl, unsigned int sw_ctl)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct snd_kcontrol_new *knew;
+
+ if (vol_ctl) {
+ knew = snd_hda_gen_add_kctl(spec, NULL, &cap_vol_temp);
+ if (!knew)
+ return -ENOMEM;
+ knew->index = idx;
+ knew->private_value = vol_ctl;
+ knew->subdevice = HDA_SUBDEV_AMP_FLAG;
+ }
+ if (sw_ctl) {
+ knew = snd_hda_gen_add_kctl(spec, NULL, &cap_sw_temp);
+ if (!knew)
+ return -ENOMEM;
+ knew->index = idx;
+ knew->private_value = sw_ctl;
+ knew->subdevice = HDA_SUBDEV_AMP_FLAG;
+ }
+ return 0;
+}
+
+/* return the vol ctl when used first in the imux list */
+static unsigned int get_first_cap_ctl(struct hda_codec *codec, int idx, int type)
+{
+ struct nid_path *path;
+ unsigned int ctl;
+ int i;
+
+ path = get_input_path(codec, 0, idx);
+ if (!path)
+ return 0;
+ ctl = path->ctls[type];
+ if (!ctl)
+ return 0;
+ for (i = 0; i < idx - 1; i++) {
+ path = get_input_path(codec, 0, i);
+ if (path && path->ctls[type] == ctl)
+ return 0;
+ }
+ return ctl;
+}
+
+/* create individual capture volume and switch controls per input */
+static int create_multi_cap_vol_ctl(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct hda_input_mux *imux = &spec->input_mux;
+ int i, err, type;
+
+ for (i = 0; i < imux->num_items; i++) {
+ bool inv_dmic;
+ int idx;
+
+ idx = imux->items[i].index;
+ if (idx >= spec->autocfg.num_inputs)
+ continue;
+ inv_dmic = is_inv_dmic_pin(codec, spec->imux_pins[i]);
+
+ for (type = 0; type < 2; type++) {
+ err = add_single_cap_ctl(codec,
+ spec->input_labels[idx],
+ spec->input_label_idxs[idx],
+ type,
+ get_first_cap_ctl(codec, i, type),
+ inv_dmic);
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int create_capture_mixers(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct hda_input_mux *imux = &spec->input_mux;
+ int i, n, nums, err;
+
+ if (spec->dyn_adc_switch)
+ nums = 1;
+ else
+ nums = spec->num_adc_nids;
+
+ if (!spec->auto_mic && imux->num_items > 1) {
+ struct snd_kcontrol_new *knew;
+ const char *name;
+ name = nums > 1 ? "Input Source" : "Capture Source";
+ knew = snd_hda_gen_add_kctl(spec, name, &cap_src_temp);
+ if (!knew)
+ return -ENOMEM;
+ knew->count = nums;
+ }
+
+ for (n = 0; n < nums; n++) {
+ bool multi = false;
+ bool multi_cap_vol = spec->multi_cap_vol;
+ bool inv_dmic = false;
+ int vol, sw;
+
+ vol = sw = 0;
+ for (i = 0; i < imux->num_items; i++) {
+ struct nid_path *path;
+ path = get_input_path(codec, n, i);
+ if (!path)
+ continue;
+ parse_capvol_in_path(codec, path);
+ if (!vol)
+ vol = path->ctls[NID_PATH_VOL_CTL];
+ else if (vol != path->ctls[NID_PATH_VOL_CTL]) {
+ multi = true;
+ if (!same_amp_caps(codec, vol,
+ path->ctls[NID_PATH_VOL_CTL], HDA_INPUT))
+ multi_cap_vol = true;
+ }
+ if (!sw)
+ sw = path->ctls[NID_PATH_MUTE_CTL];
+ else if (sw != path->ctls[NID_PATH_MUTE_CTL]) {
+ multi = true;
+ if (!same_amp_caps(codec, sw,
+ path->ctls[NID_PATH_MUTE_CTL], HDA_INPUT))
+ multi_cap_vol = true;
+ }
+ if (is_inv_dmic_pin(codec, spec->imux_pins[i]))
+ inv_dmic = true;
+ }
+
+ if (!multi)
+ err = create_single_cap_vol_ctl(codec, n, vol, sw,
+ inv_dmic);
+ else if (!multi_cap_vol && !inv_dmic)
+ err = create_bind_cap_vol_ctl(codec, n, vol, sw);
+ else
+ err = create_multi_cap_vol_ctl(codec);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * add mic boosts if needed
+ */
+
+/* check whether the given amp is feasible as a boost volume */
+static bool check_boost_vol(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx)
+{
+ unsigned int step;
+
+ if (!nid_has_volume(codec, nid, dir) ||
+ is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) ||
+ is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL))
+ return false;
+
+ step = (query_amp_caps(codec, nid, dir) & AC_AMPCAP_STEP_SIZE)
+ >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ if (step < 0x20)
+ return false;
+ return true;
+}
+
+/* look for a boost amp in a widget close to the pin */
+static unsigned int look_for_boost_amp(struct hda_codec *codec,
+ struct nid_path *path)
+{
+ unsigned int val = 0;
+ hda_nid_t nid;
+ int depth;
+
+ for (depth = 0; depth < 3; depth++) {
+ if (depth >= path->depth - 1)
+ break;
+ nid = path->path[depth];
+ if (depth && check_boost_vol(codec, nid, HDA_OUTPUT, 0)) {
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ break;
+ } else if (check_boost_vol(codec, nid, HDA_INPUT,
+ path->idx[depth])) {
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, path->idx[depth],
+ HDA_INPUT);
+ break;
+ }
+ }
+
+ return val;
+}
+
+static int parse_mic_boost(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ struct hda_input_mux *imux = &spec->input_mux;
+ int i;
+
+ if (!spec->num_adc_nids)
+ return 0;
+
+ for (i = 0; i < imux->num_items; i++) {
+ struct nid_path *path;
+ unsigned int val;
+ int idx;
+ char boost_label[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+
+ idx = imux->items[i].index;
+ if (idx >= imux->num_items)
+ continue;
+
+ /* check only line-in and mic pins */
+ if (cfg->inputs[idx].type > AUTO_PIN_LINE_IN)
+ continue;
+
+ path = get_input_path(codec, 0, i);
+ if (!path)
+ continue;
+
+ val = look_for_boost_amp(codec, path);
+ if (!val)
+ continue;
+
+ /* create a boost control */
+ snprintf(boost_label, sizeof(boost_label),
+ "%s Boost Volume", spec->input_labels[idx]);
+ if (!add_control(spec, HDA_CTL_WIDGET_VOL, boost_label,
+ spec->input_label_idxs[idx], val))
+ return -ENOMEM;
+
+ path->ctls[NID_PATH_BOOST_CTL] = val;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SND_HDA_GENERIC_LEDS
+/*
+ * vmaster mute LED hook helpers
+ */
+
+static int create_mute_led_cdev(struct hda_codec *codec,
+ int (*callback)(struct led_classdev *,
+ enum led_brightness),
+ bool micmute)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct led_classdev *cdev;
+ int idx = micmute ? LED_AUDIO_MICMUTE : LED_AUDIO_MUTE;
+ int err;
+
+ cdev = devm_kzalloc(&codec->core.dev, sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return -ENOMEM;
+
+ cdev->name = micmute ? "hda::micmute" : "hda::mute";
+ cdev->max_brightness = 1;
+ cdev->default_trigger = micmute ? "audio-micmute" : "audio-mute";
+ cdev->brightness_set_blocking = callback;
+ cdev->brightness = ledtrig_audio_get(idx);
+ cdev->flags = LED_CORE_SUSPENDRESUME;
+
+ err = led_classdev_register(&codec->core.dev, cdev);
+ if (err < 0)
+ return err;
+ spec->led_cdevs[idx] = cdev;
+ return 0;
+}
+
+static void vmaster_update_mute_led(void *private_data, int enabled)
+{
+ ledtrig_audio_set(LED_AUDIO_MUTE, enabled ? LED_OFF : LED_ON);
+}
+
+/**
+ * snd_dha_gen_add_mute_led_cdev - Create a LED classdev and enable as vmaster mute LED
+ * @codec: the HDA codec
+ * @callback: the callback for LED classdev brightness_set_blocking
+ */
+int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec,
+ int (*callback)(struct led_classdev *,
+ enum led_brightness))
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ if (callback) {
+ err = create_mute_led_cdev(codec, callback, false);
+ if (err) {
+ codec_warn(codec, "failed to create a mute LED cdev\n");
+ return err;
+ }
+ }
+
+ if (spec->vmaster_mute.hook)
+ codec_err(codec, "vmaster hook already present before cdev!\n");
+
+ spec->vmaster_mute.hook = vmaster_update_mute_led;
+ spec->vmaster_mute_enum = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_add_mute_led_cdev);
+
+/*
+ * mic mute LED hook helpers
+ */
+enum {
+ MICMUTE_LED_ON,
+ MICMUTE_LED_OFF,
+ MICMUTE_LED_FOLLOW_CAPTURE,
+ MICMUTE_LED_FOLLOW_MUTE,
+};
+
+static void call_micmute_led_update(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ unsigned int val;
+
+ switch (spec->micmute_led.led_mode) {
+ case MICMUTE_LED_ON:
+ val = 1;
+ break;
+ case MICMUTE_LED_OFF:
+ val = 0;
+ break;
+ case MICMUTE_LED_FOLLOW_CAPTURE:
+ val = !!spec->micmute_led.capture;
+ break;
+ case MICMUTE_LED_FOLLOW_MUTE:
+ default:
+ val = !spec->micmute_led.capture;
+ break;
+ }
+
+ if (val == spec->micmute_led.led_value)
+ return;
+ spec->micmute_led.led_value = val;
+ ledtrig_audio_set(LED_AUDIO_MICMUTE,
+ spec->micmute_led.led_value ? LED_ON : LED_OFF);
+}
+
+static void update_micmute_led(struct hda_codec *codec,
+ struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ unsigned int mask;
+
+ if (spec->micmute_led.old_hook)
+ spec->micmute_led.old_hook(codec, kcontrol, ucontrol);
+
+ if (!ucontrol)
+ return;
+ mask = 1U << snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (!strcmp("Capture Switch", ucontrol->id.name)) {
+ /* TODO: How do I verify if it's a mono or stereo here? */
+ if (ucontrol->value.integer.value[0] ||
+ ucontrol->value.integer.value[1])
+ spec->micmute_led.capture |= mask;
+ else
+ spec->micmute_led.capture &= ~mask;
+ call_micmute_led_update(codec);
+ }
+}
+
+static int micmute_led_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "On", "Off", "Follow Capture", "Follow Mute",
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int micmute_led_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->micmute_led.led_mode;
+ return 0;
+}
+
+static int micmute_led_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ unsigned int mode;
+
+ mode = ucontrol->value.enumerated.item[0];
+ if (mode > MICMUTE_LED_FOLLOW_MUTE)
+ mode = MICMUTE_LED_FOLLOW_MUTE;
+ if (mode == spec->micmute_led.led_mode)
+ return 0;
+ spec->micmute_led.led_mode = mode;
+ call_micmute_led_update(codec);
+ return 1;
+}
+
+static const struct snd_kcontrol_new micmute_led_mode_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Mute-LED Mode",
+ .info = micmute_led_mode_info,
+ .get = micmute_led_mode_get,
+ .put = micmute_led_mode_put,
+};
+
+/* Set up the capture sync hook for controlling the mic-mute LED */
+static int add_micmute_led_hook(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ spec->micmute_led.led_mode = MICMUTE_LED_FOLLOW_MUTE;
+ spec->micmute_led.capture = 0;
+ spec->micmute_led.led_value = -1;
+ spec->micmute_led.old_hook = spec->cap_sync_hook;
+ spec->cap_sync_hook = update_micmute_led;
+ if (!snd_hda_gen_add_kctl(spec, NULL, &micmute_led_mode_ctl))
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * snd_dha_gen_add_micmute_led_cdev - Create a LED classdev and enable as mic-mute LED
+ * @codec: the HDA codec
+ * @callback: the callback for LED classdev brightness_set_blocking
+ *
+ * Called from the codec drivers for offering the mic mute LED controls.
+ * This creates a LED classdev and sets up the cap_sync_hook that is called at
+ * each time when the capture mixer switch changes.
+ *
+ * When NULL is passed to @callback, no classdev is created but only the
+ * LED-trigger is set up.
+ *
+ * Returns 0 or a negative error.
+ */
+int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec,
+ int (*callback)(struct led_classdev *,
+ enum led_brightness))
+{
+ int err;
+
+ if (callback) {
+ err = create_mute_led_cdev(codec, callback, true);
+ if (err) {
+ codec_warn(codec, "failed to create a mic-mute LED cdev\n");
+ return err;
+ }
+ }
+
+ return add_micmute_led_hook(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_add_micmute_led_cdev);
+#endif /* CONFIG_SND_HDA_GENERIC_LEDS */
+
+/*
+ * parse digital I/Os and set up NIDs in BIOS auto-parse mode
+ */
+static void parse_digital(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+ int i, nums;
+ hda_nid_t dig_nid, pin;
+
+ /* support multiple SPDIFs; the secondary is set up as a follower */
+ nums = 0;
+ for (i = 0; i < spec->autocfg.dig_outs; i++) {
+ pin = spec->autocfg.dig_out_pins[i];
+ dig_nid = look_for_dac(codec, pin, true);
+ if (!dig_nid)
+ continue;
+ path = snd_hda_add_new_path(codec, dig_nid, pin, 0);
+ if (!path)
+ continue;
+ print_nid_path(codec, "digout", path);
+ path->active = true;
+ path->pin_fixed = true; /* no jack detection */
+ spec->digout_paths[i] = snd_hda_get_path_idx(codec, path);
+ set_pin_target(codec, pin, PIN_OUT, false);
+ if (!nums) {
+ spec->multiout.dig_out_nid = dig_nid;
+ spec->dig_out_type = spec->autocfg.dig_out_type[0];
+ } else {
+ spec->multiout.follower_dig_outs = spec->follower_dig_outs;
+ if (nums >= ARRAY_SIZE(spec->follower_dig_outs) - 1)
+ break;
+ spec->follower_dig_outs[nums - 1] = dig_nid;
+ }
+ nums++;
+ }
+
+ if (spec->autocfg.dig_in_pin) {
+ pin = spec->autocfg.dig_in_pin;
+ for_each_hda_codec_node(dig_nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, dig_nid);
+ if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
+ continue;
+ if (!(wcaps & AC_WCAP_DIGITAL))
+ continue;
+ path = snd_hda_add_new_path(codec, pin, dig_nid, 0);
+ if (path) {
+ print_nid_path(codec, "digin", path);
+ path->active = true;
+ path->pin_fixed = true; /* no jack */
+ spec->dig_in_nid = dig_nid;
+ spec->digin_path = snd_hda_get_path_idx(codec, path);
+ set_pin_target(codec, pin, PIN_IN, false);
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+ * input MUX handling
+ */
+
+static bool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur);
+
+/* select the given imux item; either unmute exclusively or select the route */
+static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
+ unsigned int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct hda_input_mux *imux;
+ struct nid_path *old_path, *path;
+
+ imux = &spec->input_mux;
+ if (!imux->num_items)
+ return 0;
+
+ if (idx >= imux->num_items)
+ idx = imux->num_items - 1;
+ if (spec->cur_mux[adc_idx] == idx)
+ return 0;
+
+ old_path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]);
+ if (!old_path)
+ return 0;
+ if (old_path->active)
+ snd_hda_activate_path(codec, old_path, false, false);
+
+ spec->cur_mux[adc_idx] = idx;
+
+ if (spec->hp_mic)
+ update_hp_mic(codec, adc_idx, false);
+
+ if (spec->dyn_adc_switch)
+ dyn_adc_pcm_resetup(codec, idx);
+
+ path = get_input_path(codec, adc_idx, idx);
+ if (!path)
+ return 0;
+ if (path->active)
+ return 0;
+ snd_hda_activate_path(codec, path, true, false);
+ if (spec->cap_sync_hook)
+ spec->cap_sync_hook(codec, NULL, NULL);
+ path_power_down_sync(codec, old_path);
+ return 1;
+}
+
+/* power up/down widgets in the all paths that match with the given NID
+ * as terminals (either start- or endpoint)
+ *
+ * returns the last changed NID, or zero if unchanged.
+ */
+static hda_nid_t set_path_power(struct hda_codec *codec, hda_nid_t nid,
+ int pin_state, int stream_state)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t last, changed = 0;
+ struct nid_path *path;
+ int n;
+
+ snd_array_for_each(&spec->paths, n, path) {
+ if (!path->depth)
+ continue;
+ if (path->path[0] == nid ||
+ path->path[path->depth - 1] == nid) {
+ bool pin_old = path->pin_enabled;
+ bool stream_old = path->stream_enabled;
+
+ if (pin_state >= 0)
+ path->pin_enabled = pin_state;
+ if (stream_state >= 0)
+ path->stream_enabled = stream_state;
+ if ((!path->pin_fixed && path->pin_enabled != pin_old)
+ || path->stream_enabled != stream_old) {
+ last = path_power_update(codec, path, true);
+ if (last)
+ changed = last;
+ }
+ }
+ }
+ return changed;
+}
+
+/* check the jack status for power control */
+static bool detect_pin_state(struct hda_codec *codec, hda_nid_t pin)
+{
+ if (!is_jack_detectable(codec, pin))
+ return true;
+ return snd_hda_jack_detect_state(codec, pin) != HDA_JACK_NOT_PRESENT;
+}
+
+/* power up/down the paths of the given pin according to the jack state;
+ * power = 0/1 : only power up/down if it matches with the jack state,
+ * < 0 : force power up/down to follow the jack sate
+ *
+ * returns the last changed NID, or zero if unchanged.
+ */
+static hda_nid_t set_pin_power_jack(struct hda_codec *codec, hda_nid_t pin,
+ int power)
+{
+ bool on;
+
+ if (!codec->power_save_node)
+ return 0;
+
+ on = detect_pin_state(codec, pin);
+
+ if (power >= 0 && on != power)
+ return 0;
+ return set_path_power(codec, pin, on, -1);
+}
+
+static void pin_power_callback(struct hda_codec *codec,
+ struct hda_jack_callback *jack,
+ bool on)
+{
+ if (jack && jack->nid)
+ sync_power_state_change(codec,
+ set_pin_power_jack(codec, jack->nid, on));
+}
+
+/* callback only doing power up -- called at first */
+static void pin_power_up_callback(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ pin_power_callback(codec, jack, true);
+}
+
+/* callback only doing power down -- called at last */
+static void pin_power_down_callback(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ pin_power_callback(codec, jack, false);
+}
+
+/* set up the power up/down callbacks */
+static void add_pin_power_ctls(struct hda_codec *codec, int num_pins,
+ const hda_nid_t *pins, bool on)
+{
+ int i;
+ hda_jack_callback_fn cb =
+ on ? pin_power_up_callback : pin_power_down_callback;
+
+ for (i = 0; i < num_pins && pins[i]; i++) {
+ if (is_jack_detectable(codec, pins[i]))
+ snd_hda_jack_detect_enable_callback(codec, pins[i], cb);
+ else
+ set_path_power(codec, pins[i], true, -1);
+ }
+}
+
+/* enabled power callback to each available I/O pin with jack detections;
+ * the digital I/O pins are excluded because of the unreliable detectsion
+ */
+static void add_all_pin_power_ctls(struct hda_codec *codec, bool on)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i;
+
+ if (!codec->power_save_node)
+ return;
+ add_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins, on);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ add_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins, on);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ add_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins, on);
+ for (i = 0; i < cfg->num_inputs; i++)
+ add_pin_power_ctls(codec, 1, &cfg->inputs[i].pin, on);
+}
+
+/* sync path power up/down with the jack states of given pins */
+static void sync_pin_power_ctls(struct hda_codec *codec, int num_pins,
+ const hda_nid_t *pins)
+{
+ int i;
+
+ for (i = 0; i < num_pins && pins[i]; i++)
+ if (is_jack_detectable(codec, pins[i]))
+ set_pin_power_jack(codec, pins[i], -1);
+}
+
+/* sync path power up/down with pins; called at init and resume */
+static void sync_all_pin_power_ctls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i;
+
+ if (!codec->power_save_node)
+ return;
+ sync_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ sync_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ sync_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins);
+ for (i = 0; i < cfg->num_inputs; i++)
+ sync_pin_power_ctls(codec, 1, &cfg->inputs[i].pin);
+}
+
+/* add fake paths if not present yet */
+static int add_fake_paths(struct hda_codec *codec, hda_nid_t nid,
+ int num_pins, const hda_nid_t *pins)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+ int i;
+
+ for (i = 0; i < num_pins; i++) {
+ if (!pins[i])
+ break;
+ if (get_nid_path(codec, nid, pins[i], 0))
+ continue;
+ path = snd_array_new(&spec->paths);
+ if (!path)
+ return -ENOMEM;
+ memset(path, 0, sizeof(*path));
+ path->depth = 2;
+ path->path[0] = nid;
+ path->path[1] = pins[i];
+ path->active = true;
+ }
+ return 0;
+}
+
+/* create fake paths to all outputs from beep */
+static int add_fake_beep_paths(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ hda_nid_t nid = spec->beep_nid;
+ int err;
+
+ if (!codec->power_save_node || !nid)
+ return 0;
+ err = add_fake_paths(codec, nid, cfg->line_outs, cfg->line_out_pins);
+ if (err < 0)
+ return err;
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+ err = add_fake_paths(codec, nid, cfg->hp_outs, cfg->hp_pins);
+ if (err < 0)
+ return err;
+ }
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ err = add_fake_paths(codec, nid, cfg->speaker_outs,
+ cfg->speaker_pins);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* power up/down beep widget and its output paths */
+static void beep_power_hook(struct hda_beep *beep, bool on)
+{
+ set_path_power(beep->codec, beep->nid, -1, on);
+}
+
+/**
+ * snd_hda_gen_fix_pin_power - Fix the power of the given pin widget to D0
+ * @codec: the HDA codec
+ * @pin: NID of pin to fix
+ */
+int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+
+ path = snd_array_new(&spec->paths);
+ if (!path)
+ return -ENOMEM;
+ memset(path, 0, sizeof(*path));
+ path->depth = 1;
+ path->path[0] = pin;
+ path->active = true;
+ path->pin_fixed = true;
+ path->stream_enabled = true;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_fix_pin_power);
+
+/*
+ * Jack detections for HP auto-mute and mic-switch
+ */
+
+/* check each pin in the given array; returns true if any of them is plugged */
+static bool detect_jacks(struct hda_codec *codec, int num_pins, const hda_nid_t *pins)
+{
+ int i;
+ bool present = false;
+
+ for (i = 0; i < num_pins; i++) {
+ hda_nid_t nid = pins[i];
+ if (!nid)
+ break;
+ /* don't detect pins retasked as inputs */
+ if (snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_IN_EN)
+ continue;
+ if (snd_hda_jack_detect_state(codec, nid) == HDA_JACK_PRESENT)
+ present = true;
+ }
+ return present;
+}
+
+/* standard HP/line-out auto-mute helper */
+static void do_automute(struct hda_codec *codec, int num_pins, const hda_nid_t *pins,
+ int *paths, bool mute)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < num_pins; i++) {
+ hda_nid_t nid = pins[i];
+ unsigned int val, oldval;
+ if (!nid)
+ break;
+
+ oldval = snd_hda_codec_get_pin_target(codec, nid);
+ if (oldval & PIN_IN)
+ continue; /* no mute for inputs */
+
+ if (spec->auto_mute_via_amp) {
+ struct nid_path *path;
+ hda_nid_t mute_nid;
+
+ path = snd_hda_get_path_from_idx(codec, paths[i]);
+ if (!path)
+ continue;
+ mute_nid = get_amp_nid_(path->ctls[NID_PATH_MUTE_CTL]);
+ if (!mute_nid)
+ continue;
+ if (mute)
+ spec->mute_bits |= (1ULL << mute_nid);
+ else
+ spec->mute_bits &= ~(1ULL << mute_nid);
+ continue;
+ } else {
+ /* don't reset VREF value in case it's controlling
+ * the amp (see alc861_fixup_asus_amp_vref_0f())
+ */
+ if (spec->keep_vref_in_automute)
+ val = oldval & ~PIN_HP;
+ else
+ val = 0;
+ if (!mute)
+ val |= oldval;
+ /* here we call update_pin_ctl() so that the pinctl is
+ * changed without changing the pinctl target value;
+ * the original target value will be still referred at
+ * the init / resume again
+ */
+ update_pin_ctl(codec, nid, val);
+ }
+
+ set_pin_eapd(codec, nid, !mute);
+ if (codec->power_save_node) {
+ bool on = !mute;
+ if (on)
+ on = detect_pin_state(codec, nid);
+ set_path_power(codec, nid, on, -1);
+ }
+ }
+}
+
+/**
+ * snd_hda_gen_update_outputs - Toggle outputs muting
+ * @codec: the HDA codec
+ *
+ * Update the mute status of all outputs based on the current jack states.
+ */
+void snd_hda_gen_update_outputs(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int *paths;
+ int on;
+
+ /* Control HP pins/amps depending on master_mute state;
+ * in general, HP pins/amps control should be enabled in all cases,
+ * but currently set only for master_mute, just to be safe
+ */
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ paths = spec->out_paths;
+ else
+ paths = spec->hp_paths;
+ do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
+ spec->autocfg.hp_pins, paths, spec->master_mute);
+
+ if (!spec->automute_speaker)
+ on = 0;
+ else
+ on = spec->hp_jack_present | spec->line_jack_present;
+ on |= spec->master_mute;
+ spec->speaker_muted = on;
+ if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
+ paths = spec->out_paths;
+ else
+ paths = spec->speaker_paths;
+ do_automute(codec, ARRAY_SIZE(spec->autocfg.speaker_pins),
+ spec->autocfg.speaker_pins, paths, on);
+
+ /* toggle line-out mutes if needed, too */
+ /* if LO is a copy of either HP or Speaker, don't need to handle it */
+ if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0] ||
+ spec->autocfg.line_out_pins[0] == spec->autocfg.speaker_pins[0])
+ return;
+ if (!spec->automute_lo)
+ on = 0;
+ else
+ on = spec->hp_jack_present;
+ on |= spec->master_mute;
+ spec->line_out_muted = on;
+ paths = spec->out_paths;
+ do_automute(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
+ spec->autocfg.line_out_pins, paths, on);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_update_outputs);
+
+static void call_update_outputs(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->automute_hook)
+ spec->automute_hook(codec);
+ else
+ snd_hda_gen_update_outputs(codec);
+
+ /* sync the whole vmaster followers to reflect the new auto-mute status */
+ if (spec->auto_mute_via_amp && !codec->bus->shutdown)
+ snd_ctl_sync_vmaster(spec->vmaster_mute.sw_kctl, false);
+}
+
+/**
+ * snd_hda_gen_hp_automute - standard HP-automute helper
+ * @codec: the HDA codec
+ * @jack: jack object, NULL for the whole
+ */
+void snd_hda_gen_hp_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t *pins = spec->autocfg.hp_pins;
+ int num_pins = ARRAY_SIZE(spec->autocfg.hp_pins);
+
+ /* No detection for the first HP jack during indep-HP mode */
+ if (spec->indep_hp_enabled) {
+ pins++;
+ num_pins--;
+ }
+
+ spec->hp_jack_present = detect_jacks(codec, num_pins, pins);
+ if (!spec->detect_hp || (!spec->automute_speaker && !spec->automute_lo))
+ return;
+ call_update_outputs(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_hp_automute);
+
+/**
+ * snd_hda_gen_line_automute - standard line-out-automute helper
+ * @codec: the HDA codec
+ * @jack: jack object, NULL for the whole
+ */
+void snd_hda_gen_line_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
+ return;
+ /* check LO jack only when it's different from HP */
+ if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0])
+ return;
+
+ spec->line_jack_present =
+ detect_jacks(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
+ spec->autocfg.line_out_pins);
+ if (!spec->automute_speaker || !spec->detect_lo)
+ return;
+ call_update_outputs(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_line_automute);
+
+/**
+ * snd_hda_gen_mic_autoswitch - standard mic auto-switch helper
+ * @codec: the HDA codec
+ * @jack: jack object, NULL for the whole
+ */
+void snd_hda_gen_mic_autoswitch(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ if (!spec->auto_mic)
+ return;
+
+ for (i = spec->am_num_entries - 1; i > 0; i--) {
+ hda_nid_t pin = spec->am_entry[i].pin;
+ /* don't detect pins retasked as outputs */
+ if (snd_hda_codec_get_pin_target(codec, pin) & AC_PINCTL_OUT_EN)
+ continue;
+ if (snd_hda_jack_detect_state(codec, pin) == HDA_JACK_PRESENT) {
+ mux_select(codec, 0, spec->am_entry[i].idx);
+ return;
+ }
+ }
+ mux_select(codec, 0, spec->am_entry[0].idx);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_mic_autoswitch);
+
+/* call appropriate hooks */
+static void call_hp_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->hp_automute_hook)
+ spec->hp_automute_hook(codec, jack);
+ else
+ snd_hda_gen_hp_automute(codec, jack);
+}
+
+static void call_line_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->line_automute_hook)
+ spec->line_automute_hook(codec, jack);
+ else
+ snd_hda_gen_line_automute(codec, jack);
+}
+
+static void call_mic_autoswitch(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->mic_autoswitch_hook)
+ spec->mic_autoswitch_hook(codec, jack);
+ else
+ snd_hda_gen_mic_autoswitch(codec, jack);
+}
+
+/* update jack retasking */
+static void update_automute_all(struct hda_codec *codec)
+{
+ call_hp_automute(codec, NULL);
+ call_line_automute(codec, NULL);
+ call_mic_autoswitch(codec, NULL);
+}
+
+/*
+ * Auto-Mute mode mixer enum support
+ */
+static int automute_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ static const char * const texts3[] = {
+ "Disabled", "Speaker Only", "Line Out+Speaker"
+ };
+
+ if (spec->automute_speaker_possible && spec->automute_lo_possible)
+ return snd_hda_enum_helper_info(kcontrol, uinfo, 3, texts3);
+ return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
+}
+
+static int automute_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ unsigned int val = 0;
+ if (spec->automute_speaker)
+ val++;
+ if (spec->automute_lo)
+ val++;
+
+ ucontrol->value.enumerated.item[0] = val;
+ return 0;
+}
+
+static int automute_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+
+ switch (ucontrol->value.enumerated.item[0]) {
+ case 0:
+ if (!spec->automute_speaker && !spec->automute_lo)
+ return 0;
+ spec->automute_speaker = 0;
+ spec->automute_lo = 0;
+ break;
+ case 1:
+ if (spec->automute_speaker_possible) {
+ if (!spec->automute_lo && spec->automute_speaker)
+ return 0;
+ spec->automute_speaker = 1;
+ spec->automute_lo = 0;
+ } else if (spec->automute_lo_possible) {
+ if (spec->automute_lo)
+ return 0;
+ spec->automute_lo = 1;
+ } else
+ return -EINVAL;
+ break;
+ case 2:
+ if (!spec->automute_lo_possible || !spec->automute_speaker_possible)
+ return -EINVAL;
+ if (spec->automute_speaker && spec->automute_lo)
+ return 0;
+ spec->automute_speaker = 1;
+ spec->automute_lo = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ call_update_outputs(codec);
+ return 1;
+}
+
+static const struct snd_kcontrol_new automute_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Auto-Mute Mode",
+ .info = automute_mode_info,
+ .get = automute_mode_get,
+ .put = automute_mode_put,
+};
+
+static int add_automute_mode_enum(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!snd_hda_gen_add_kctl(spec, NULL, &automute_mode_enum))
+ return -ENOMEM;
+ return 0;
+}
+
+/*
+ * Check the availability of HP/line-out auto-mute;
+ * Set up appropriately if really supported
+ */
+static int check_auto_mute_availability(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int present = 0;
+ int i, err;
+
+ if (spec->suppress_auto_mute)
+ return 0;
+
+ if (cfg->hp_pins[0])
+ present++;
+ if (cfg->line_out_pins[0])
+ present++;
+ if (cfg->speaker_pins[0])
+ present++;
+ if (present < 2) /* need two different output types */
+ return 0;
+
+ if (!cfg->speaker_pins[0] &&
+ cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
+ memcpy(cfg->speaker_pins, cfg->line_out_pins,
+ sizeof(cfg->speaker_pins));
+ cfg->speaker_outs = cfg->line_outs;
+ }
+
+ if (!cfg->hp_pins[0] &&
+ cfg->line_out_type == AUTO_PIN_HP_OUT) {
+ memcpy(cfg->hp_pins, cfg->line_out_pins,
+ sizeof(cfg->hp_pins));
+ cfg->hp_outs = cfg->line_outs;
+ }
+
+ for (i = 0; i < cfg->hp_outs; i++) {
+ hda_nid_t nid = cfg->hp_pins[i];
+ if (!is_jack_detectable(codec, nid))
+ continue;
+ codec_dbg(codec, "Enable HP auto-muting on NID 0x%x\n", nid);
+ snd_hda_jack_detect_enable_callback(codec, nid,
+ call_hp_automute);
+ spec->detect_hp = 1;
+ }
+
+ if (cfg->line_out_type == AUTO_PIN_LINE_OUT && cfg->line_outs) {
+ if (cfg->speaker_outs)
+ for (i = 0; i < cfg->line_outs; i++) {
+ hda_nid_t nid = cfg->line_out_pins[i];
+ if (!is_jack_detectable(codec, nid))
+ continue;
+ codec_dbg(codec, "Enable Line-Out auto-muting on NID 0x%x\n", nid);
+ snd_hda_jack_detect_enable_callback(codec, nid,
+ call_line_automute);
+ spec->detect_lo = 1;
+ }
+ spec->automute_lo_possible = spec->detect_hp;
+ }
+
+ spec->automute_speaker_possible = cfg->speaker_outs &&
+ (spec->detect_hp || spec->detect_lo);
+
+ spec->automute_lo = spec->automute_lo_possible;
+ spec->automute_speaker = spec->automute_speaker_possible;
+
+ if (spec->automute_speaker_possible || spec->automute_lo_possible) {
+ /* create a control for automute mode */
+ err = add_automute_mode_enum(codec);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* check whether all auto-mic pins are valid; setup indices if OK */
+static bool auto_mic_check_imux(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct hda_input_mux *imux;
+ int i;
+
+ imux = &spec->input_mux;
+ for (i = 0; i < spec->am_num_entries; i++) {
+ spec->am_entry[i].idx =
+ find_idx_in_nid_list(spec->am_entry[i].pin,
+ spec->imux_pins, imux->num_items);
+ if (spec->am_entry[i].idx < 0)
+ return false; /* no corresponding imux */
+ }
+
+ /* we don't need the jack detection for the first pin */
+ for (i = 1; i < spec->am_num_entries; i++)
+ snd_hda_jack_detect_enable_callback(codec,
+ spec->am_entry[i].pin,
+ call_mic_autoswitch);
+ return true;
+}
+
+static int compare_attr(const void *ap, const void *bp)
+{
+ const struct automic_entry *a = ap;
+ const struct automic_entry *b = bp;
+ return (int)(a->attr - b->attr);
+}
+
+/*
+ * Check the availability of auto-mic switch;
+ * Set up if really supported
+ */
+static int check_auto_mic_availability(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int types;
+ int i, num_pins;
+
+ if (spec->suppress_auto_mic)
+ return 0;
+
+ types = 0;
+ num_pins = 0;
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t nid = cfg->inputs[i].pin;
+ unsigned int attr;
+ attr = snd_hda_codec_get_pincfg(codec, nid);
+ attr = snd_hda_get_input_pin_attr(attr);
+ if (types & (1 << attr))
+ return 0; /* already occupied */
+ switch (attr) {
+ case INPUT_PIN_ATTR_INT:
+ if (cfg->inputs[i].type != AUTO_PIN_MIC)
+ return 0; /* invalid type */
+ break;
+ case INPUT_PIN_ATTR_UNUSED:
+ return 0; /* invalid entry */
+ default:
+ if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
+ return 0; /* invalid type */
+ if (!spec->line_in_auto_switch &&
+ cfg->inputs[i].type != AUTO_PIN_MIC)
+ return 0; /* only mic is allowed */
+ if (!is_jack_detectable(codec, nid))
+ return 0; /* no unsol support */
+ break;
+ }
+ if (num_pins >= MAX_AUTO_MIC_PINS)
+ return 0;
+ types |= (1 << attr);
+ spec->am_entry[num_pins].pin = nid;
+ spec->am_entry[num_pins].attr = attr;
+ num_pins++;
+ }
+
+ if (num_pins < 2)
+ return 0;
+
+ spec->am_num_entries = num_pins;
+ /* sort the am_entry in the order of attr so that the pin with a
+ * higher attr will be selected when the jack is plugged.
+ */
+ sort(spec->am_entry, num_pins, sizeof(spec->am_entry[0]),
+ compare_attr, NULL);
+
+ if (!auto_mic_check_imux(codec))
+ return 0;
+
+ spec->auto_mic = 1;
+ spec->num_adc_nids = 1;
+ spec->cur_mux[0] = spec->am_entry[0].idx;
+ codec_dbg(codec, "Enable auto-mic switch on NID 0x%x/0x%x/0x%x\n",
+ spec->am_entry[0].pin,
+ spec->am_entry[1].pin,
+ spec->am_entry[2].pin);
+
+ return 0;
+}
+
+/**
+ * snd_hda_gen_path_power_filter - power_filter hook to make inactive widgets
+ * into power down
+ * @codec: the HDA codec
+ * @nid: NID to evalute
+ * @power_state: target power state
+ */
+unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!spec->power_down_unused && !codec->power_save_node)
+ return power_state;
+ if (power_state != AC_PWRST_D0 || nid == codec->core.afg)
+ return power_state;
+ if (get_wcaps_type(get_wcaps(codec, nid)) >= AC_WID_POWER)
+ return power_state;
+ if (is_active_nid_for_any(codec, nid))
+ return power_state;
+ return AC_PWRST_D3;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_path_power_filter);
+
+/* mute all aamix inputs initially; parse up to the first leaves */
+static void mute_all_mixer_nid(struct hda_codec *codec, hda_nid_t mix)
+{
+ int i, nums;
+ const hda_nid_t *conn;
+ bool has_amp;
+
+ nums = snd_hda_get_conn_list(codec, mix, &conn);
+ has_amp = nid_has_mute(codec, mix, HDA_INPUT);
+ for (i = 0; i < nums; i++) {
+ if (has_amp)
+ update_amp(codec, mix, HDA_INPUT, i,
+ 0xff, HDA_AMP_MUTE);
+ else if (nid_has_volume(codec, conn[i], HDA_OUTPUT))
+ update_amp(codec, conn[i], HDA_OUTPUT, 0,
+ 0xff, HDA_AMP_MUTE);
+ }
+}
+
+/**
+ * snd_hda_gen_stream_pm - Stream power management callback
+ * @codec: the HDA codec
+ * @nid: audio widget
+ * @on: power on/off flag
+ *
+ * Set this in patch_ops.stream_pm. Only valid with power_save_node flag.
+ */
+void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on)
+{
+ if (codec->power_save_node)
+ set_path_power(codec, nid, -1, on);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_stream_pm);
+
+/**
+ * snd_hda_gen_parse_auto_config - Parse the given BIOS configuration and
+ * set up the hda_gen_spec
+ * @codec: the HDA codec
+ * @cfg: Parsed pin configuration
+ *
+ * return 1 if successful, 0 if the proper config is not found,
+ * or a negative error code
+ */
+int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ parse_user_hints(codec);
+
+ if (spec->mixer_nid && !spec->mixer_merge_nid)
+ spec->mixer_merge_nid = spec->mixer_nid;
+
+ if (cfg != &spec->autocfg) {
+ spec->autocfg = *cfg;
+ cfg = &spec->autocfg;
+ }
+
+ if (!spec->main_out_badness)
+ spec->main_out_badness = &hda_main_out_badness;
+ if (!spec->extra_out_badness)
+ spec->extra_out_badness = &hda_extra_out_badness;
+
+ fill_all_dac_nids(codec);
+
+ if (!cfg->line_outs) {
+ if (cfg->dig_outs || cfg->dig_in_pin) {
+ spec->multiout.max_channels = 2;
+ spec->no_analog = 1;
+ goto dig_only;
+ }
+ if (!cfg->num_inputs && !cfg->dig_in_pin)
+ return 0; /* can't find valid BIOS pin config */
+ }
+
+ if (!spec->no_primary_hp &&
+ cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
+ cfg->line_outs <= cfg->hp_outs) {
+ /* use HP as primary out */
+ cfg->speaker_outs = cfg->line_outs;
+ memcpy(cfg->speaker_pins, cfg->line_out_pins,
+ sizeof(cfg->speaker_pins));
+ cfg->line_outs = cfg->hp_outs;
+ memcpy(cfg->line_out_pins, cfg->hp_pins, sizeof(cfg->hp_pins));
+ cfg->hp_outs = 0;
+ memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
+ cfg->line_out_type = AUTO_PIN_HP_OUT;
+ }
+
+ err = parse_output_paths(codec);
+ if (err < 0)
+ return err;
+ err = create_multi_channel_mode(codec);
+ if (err < 0)
+ return err;
+ err = create_multi_out_ctls(codec, cfg);
+ if (err < 0)
+ return err;
+ err = create_hp_out_ctls(codec);
+ if (err < 0)
+ return err;
+ err = create_speaker_out_ctls(codec);
+ if (err < 0)
+ return err;
+ err = create_indep_hp_ctls(codec);
+ if (err < 0)
+ return err;
+ err = create_loopback_mixing_ctl(codec);
+ if (err < 0)
+ return err;
+ err = create_hp_mic(codec);
+ if (err < 0)
+ return err;
+ err = create_input_ctls(codec);
+ if (err < 0)
+ return err;
+
+ /* add power-down pin callbacks at first */
+ add_all_pin_power_ctls(codec, false);
+
+ spec->const_channel_count = spec->ext_channel_count;
+ /* check the multiple speaker and headphone pins */
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ spec->const_channel_count = max(spec->const_channel_count,
+ cfg->speaker_outs * 2);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ spec->const_channel_count = max(spec->const_channel_count,
+ cfg->hp_outs * 2);
+ spec->multiout.max_channels = max(spec->ext_channel_count,
+ spec->const_channel_count);
+
+ err = check_auto_mute_availability(codec);
+ if (err < 0)
+ return err;
+
+ err = check_dyn_adc_switch(codec);
+ if (err < 0)
+ return err;
+
+ err = check_auto_mic_availability(codec);
+ if (err < 0)
+ return err;
+
+ /* add stereo mix if available and not enabled yet */
+ if (!spec->auto_mic && spec->mixer_nid &&
+ spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_AUTO &&
+ spec->input_mux.num_items > 1) {
+ err = parse_capture_source(codec, spec->mixer_nid,
+ CFG_IDX_MIX, spec->num_all_adcs,
+ "Stereo Mix", 0);
+ if (err < 0)
+ return err;
+ }
+
+
+ err = create_capture_mixers(codec);
+ if (err < 0)
+ return err;
+
+ err = parse_mic_boost(codec);
+ if (err < 0)
+ return err;
+
+ /* create "Headphone Mic Jack Mode" if no input selection is
+ * available (or user specifies add_jack_modes hint)
+ */
+ if (spec->hp_mic_pin &&
+ (spec->auto_mic || spec->input_mux.num_items == 1 ||
+ spec->add_jack_modes)) {
+ err = create_hp_mic_jack_mode(codec, spec->hp_mic_pin);
+ if (err < 0)
+ return err;
+ }
+
+ if (spec->add_jack_modes) {
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ err = create_out_jack_modes(codec, cfg->line_outs,
+ cfg->line_out_pins);
+ if (err < 0)
+ return err;
+ }
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+ err = create_out_jack_modes(codec, cfg->hp_outs,
+ cfg->hp_pins);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ /* add power-up pin callbacks at last */
+ add_all_pin_power_ctls(codec, true);
+
+ /* mute all aamix input initially */
+ if (spec->mixer_nid)
+ mute_all_mixer_nid(codec, spec->mixer_nid);
+
+ dig_only:
+ parse_digital(codec);
+
+ if (spec->power_down_unused || codec->power_save_node) {
+ if (!codec->power_filter)
+ codec->power_filter = snd_hda_gen_path_power_filter;
+ if (!codec->patch_ops.stream_pm)
+ codec->patch_ops.stream_pm = snd_hda_gen_stream_pm;
+ }
+
+ if (!spec->no_analog && spec->beep_nid) {
+ err = snd_hda_attach_beep_device(codec, spec->beep_nid);
+ if (err < 0)
+ return err;
+ if (codec->beep && codec->power_save_node) {
+ err = add_fake_beep_paths(codec);
+ if (err < 0)
+ return err;
+ codec->beep->power_hook = beep_power_hook;
+ }
+ }
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_parse_auto_config);
+
+
+/*
+ * Build control elements
+ */
+
+/* follower controls for virtual master */
+static const char * const follower_pfxs[] = {
+ "Front", "Surround", "Center", "LFE", "Side",
+ "Headphone", "Speaker", "Mono", "Line Out",
+ "CLFE", "Bass Speaker", "PCM",
+ "Speaker Front", "Speaker Surround", "Speaker CLFE", "Speaker Side",
+ "Headphone Front", "Headphone Surround", "Headphone CLFE",
+ "Headphone Side", "Headphone+LO", "Speaker+LO",
+ NULL,
+};
+
+/**
+ * snd_hda_gen_build_controls - Build controls from the parsed results
+ * @codec: the HDA codec
+ *
+ * Pass this to build_controls patch_ops.
+ */
+int snd_hda_gen_build_controls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ if (spec->kctls.used) {
+ err = snd_hda_add_new_ctls(codec, spec->kctls.list);
+ if (err < 0)
+ return err;
+ }
+
+ if (spec->multiout.dig_out_nid) {
+ err = snd_hda_create_dig_out_ctls(codec,
+ spec->multiout.dig_out_nid,
+ spec->multiout.dig_out_nid,
+ spec->pcm_rec[1]->pcm_type);
+ if (err < 0)
+ return err;
+ if (!spec->no_analog) {
+ err = snd_hda_create_spdif_share_sw(codec,
+ &spec->multiout);
+ if (err < 0)
+ return err;
+ spec->multiout.share_spdif = 1;
+ }
+ }
+ if (spec->dig_in_nid) {
+ err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+ if (err < 0)
+ return err;
+ }
+
+ /* if we have no master control, let's create it */
+ if (!spec->no_analog && !spec->suppress_vmaster &&
+ !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
+ err = snd_hda_add_vmaster(codec, "Master Playback Volume",
+ spec->vmaster_tlv, follower_pfxs,
+ "Playback Volume");
+ if (err < 0)
+ return err;
+ }
+ if (!spec->no_analog && !spec->suppress_vmaster &&
+ !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
+ err = __snd_hda_add_vmaster(codec, "Master Playback Switch",
+ NULL, follower_pfxs,
+ "Playback Switch",
+ true, &spec->vmaster_mute.sw_kctl);
+ if (err < 0)
+ return err;
+ if (spec->vmaster_mute.hook) {
+ snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute,
+ spec->vmaster_mute_enum);
+ snd_hda_sync_vmaster_hook(&spec->vmaster_mute);
+ }
+ }
+
+ free_kctls(spec); /* no longer needed */
+
+ err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_build_controls);
+
+
+/*
+ * PCM definitions
+ */
+
+static void call_pcm_playback_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->pcm_playback_hook)
+ spec->pcm_playback_hook(hinfo, codec, substream, action);
+}
+
+static void call_pcm_capture_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->pcm_capture_hook)
+ spec->pcm_capture_hook(hinfo, codec, substream, action);
+}
+
+/*
+ * Analog playback callbacks
+ */
+static int playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ mutex_lock(&spec->pcm_mutex);
+ err = snd_hda_multi_out_analog_open(codec,
+ &spec->multiout, substream,
+ hinfo);
+ if (!err) {
+ spec->active_streams |= 1 << STREAM_MULTI_OUT;
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_OPEN);
+ }
+ mutex_unlock(&spec->pcm_mutex);
+ return err;
+}
+
+static int playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ err = snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+ stream_tag, format, substream);
+ if (!err)
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return err;
+}
+
+static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ err = snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+ if (!err)
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return err;
+}
+
+static int playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ mutex_lock(&spec->pcm_mutex);
+ spec->active_streams &= ~(1 << STREAM_MULTI_OUT);
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLOSE);
+ mutex_unlock(&spec->pcm_mutex);
+ return 0;
+}
+
+static int capture_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_OPEN);
+ return 0;
+}
+
+static int capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return 0;
+}
+
+static int capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return 0;
+}
+
+static int capture_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLOSE);
+ return 0;
+}
+
+static int alt_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err = 0;
+
+ mutex_lock(&spec->pcm_mutex);
+ if (spec->indep_hp && !spec->indep_hp_enabled)
+ err = -EBUSY;
+ else
+ spec->active_streams |= 1 << STREAM_INDEP_HP;
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_OPEN);
+ mutex_unlock(&spec->pcm_mutex);
+ return err;
+}
+
+static int alt_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ mutex_lock(&spec->pcm_mutex);
+ spec->active_streams &= ~(1 << STREAM_INDEP_HP);
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLOSE);
+ mutex_unlock(&spec->pcm_mutex);
+ return 0;
+}
+
+static int alt_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return 0;
+}
+
+static int alt_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return 0;
+}
+
+/*
+ * Digital out
+ */
+static int dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+ stream_tag, format, substream);
+}
+
+static int dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+}
+
+static int dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+#define alt_capture_pcm_open capture_pcm_open
+#define alt_capture_pcm_close capture_pcm_close
+
+static int alt_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number + 1],
+ stream_tag, 0, format);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return 0;
+}
+
+static int alt_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ snd_hda_codec_cleanup_stream(codec,
+ spec->adc_nids[substream->number + 1]);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return 0;
+}
+
+/*
+ */
+static const struct hda_pcm_stream pcm_analog_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 8,
+ /* NID is set in build_pcms */
+ .ops = {
+ .open = playback_pcm_open,
+ .close = playback_pcm_close,
+ .prepare = playback_pcm_prepare,
+ .cleanup = playback_pcm_cleanup
+ },
+};
+
+static const struct hda_pcm_stream pcm_analog_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in build_pcms */
+ .ops = {
+ .open = capture_pcm_open,
+ .close = capture_pcm_close,
+ .prepare = capture_pcm_prepare,
+ .cleanup = capture_pcm_cleanup
+ },
+};
+
+static const struct hda_pcm_stream pcm_analog_alt_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in build_pcms */
+ .ops = {
+ .open = alt_playback_pcm_open,
+ .close = alt_playback_pcm_close,
+ .prepare = alt_playback_pcm_prepare,
+ .cleanup = alt_playback_pcm_cleanup
+ },
+};
+
+static const struct hda_pcm_stream pcm_analog_alt_capture = {
+ .substreams = 2, /* can be overridden */
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in build_pcms */
+ .ops = {
+ .open = alt_capture_pcm_open,
+ .close = alt_capture_pcm_close,
+ .prepare = alt_capture_pcm_prepare,
+ .cleanup = alt_capture_pcm_cleanup
+ },
+};
+
+static const struct hda_pcm_stream pcm_digital_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in build_pcms */
+ .ops = {
+ .open = dig_playback_pcm_open,
+ .close = dig_playback_pcm_close,
+ .prepare = dig_playback_pcm_prepare,
+ .cleanup = dig_playback_pcm_cleanup
+ },
+};
+
+static const struct hda_pcm_stream pcm_digital_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in build_pcms */
+};
+
+/* Used by build_pcms to flag that a PCM has no playback stream */
+static const struct hda_pcm_stream pcm_null_stream = {
+ .substreams = 0,
+ .channels_min = 0,
+ .channels_max = 0,
+};
+
+/*
+ * dynamic changing ADC PCM streams
+ */
+static bool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t new_adc = spec->adc_nids[spec->dyn_adc_idx[cur]];
+
+ if (spec->cur_adc && spec->cur_adc != new_adc) {
+ /* stream is running, let's swap the current ADC */
+ __snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
+ spec->cur_adc = new_adc;
+ snd_hda_codec_setup_stream(codec, new_adc,
+ spec->cur_adc_stream_tag, 0,
+ spec->cur_adc_format);
+ return true;
+ }
+ return false;
+}
+
+/* analog capture with dynamic dual-adc changes */
+static int dyn_adc_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ spec->cur_adc = spec->adc_nids[spec->dyn_adc_idx[spec->cur_mux[0]]];
+ spec->cur_adc_stream_tag = stream_tag;
+ spec->cur_adc_format = format;
+ snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
+ call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_PREPARE);
+ return 0;
+}
+
+static int dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
+ spec->cur_adc = 0;
+ call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLEANUP);
+ return 0;
+}
+
+static const struct hda_pcm_stream dyn_adc_pcm_analog_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = 0, /* fill later */
+ .ops = {
+ .prepare = dyn_adc_capture_pcm_prepare,
+ .cleanup = dyn_adc_capture_pcm_cleanup
+ },
+};
+
+static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
+ const char *chip_name)
+{
+ char *p;
+
+ if (*str)
+ return;
+ strlcpy(str, chip_name, len);
+
+ /* drop non-alnum chars after a space */
+ for (p = strchr(str, ' '); p; p = strchr(p + 1, ' ')) {
+ if (!isalnum(p[1])) {
+ *p = 0;
+ break;
+ }
+ }
+ strlcat(str, sfx, len);
+}
+
+/* copy PCM stream info from @default_str, and override non-NULL entries
+ * from @spec_str and @nid
+ */
+static void setup_pcm_stream(struct hda_pcm_stream *str,
+ const struct hda_pcm_stream *default_str,
+ const struct hda_pcm_stream *spec_str,
+ hda_nid_t nid)
+{
+ *str = *default_str;
+ if (nid)
+ str->nid = nid;
+ if (spec_str) {
+ if (spec_str->substreams)
+ str->substreams = spec_str->substreams;
+ if (spec_str->channels_min)
+ str->channels_min = spec_str->channels_min;
+ if (spec_str->channels_max)
+ str->channels_max = spec_str->channels_max;
+ if (spec_str->rates)
+ str->rates = spec_str->rates;
+ if (spec_str->formats)
+ str->formats = spec_str->formats;
+ if (spec_str->maxbps)
+ str->maxbps = spec_str->maxbps;
+ }
+}
+
+/**
+ * snd_hda_gen_build_pcms - build PCM streams based on the parsed results
+ * @codec: the HDA codec
+ *
+ * Pass this to build_pcms patch_ops.
+ */
+int snd_hda_gen_build_pcms(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct hda_pcm *info;
+ bool have_multi_adcs;
+
+ if (spec->no_analog)
+ goto skip_analog;
+
+ fill_pcm_stream_name(spec->stream_name_analog,
+ sizeof(spec->stream_name_analog),
+ " Analog", codec->core.chip_name);
+ info = snd_hda_codec_pcm_new(codec, "%s", spec->stream_name_analog);
+ if (!info)
+ return -ENOMEM;
+ spec->pcm_rec[0] = info;
+
+ if (spec->multiout.num_dacs > 0) {
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+ &pcm_analog_playback,
+ spec->stream_analog_playback,
+ spec->multiout.dac_nids[0]);
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+ spec->multiout.max_channels;
+ if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT &&
+ spec->autocfg.line_outs == 2)
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap =
+ snd_pcm_2_1_chmaps;
+ }
+ if (spec->num_adc_nids) {
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+ (spec->dyn_adc_switch ?
+ &dyn_adc_pcm_analog_capture : &pcm_analog_capture),
+ spec->stream_analog_capture,
+ spec->adc_nids[0]);
+ }
+
+ skip_analog:
+ /* SPDIF for stream index #1 */
+ if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+ fill_pcm_stream_name(spec->stream_name_digital,
+ sizeof(spec->stream_name_digital),
+ " Digital", codec->core.chip_name);
+ info = snd_hda_codec_pcm_new(codec, "%s",
+ spec->stream_name_digital);
+ if (!info)
+ return -ENOMEM;
+ codec->follower_dig_outs = spec->multiout.follower_dig_outs;
+ spec->pcm_rec[1] = info;
+ if (spec->dig_out_type)
+ info->pcm_type = spec->dig_out_type;
+ else
+ info->pcm_type = HDA_PCM_TYPE_SPDIF;
+ if (spec->multiout.dig_out_nid)
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+ &pcm_digital_playback,
+ spec->stream_digital_playback,
+ spec->multiout.dig_out_nid);
+ if (spec->dig_in_nid)
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+ &pcm_digital_capture,
+ spec->stream_digital_capture,
+ spec->dig_in_nid);
+ }
+
+ if (spec->no_analog)
+ return 0;
+
+ /* If the use of more than one ADC is requested for the current
+ * model, configure a second analog capture-only PCM.
+ */
+ have_multi_adcs = (spec->num_adc_nids > 1) &&
+ !spec->dyn_adc_switch && !spec->auto_mic;
+ /* Additional Analaog capture for index #2 */
+ if (spec->alt_dac_nid || have_multi_adcs) {
+ fill_pcm_stream_name(spec->stream_name_alt_analog,
+ sizeof(spec->stream_name_alt_analog),
+ " Alt Analog", codec->core.chip_name);
+ info = snd_hda_codec_pcm_new(codec, "%s",
+ spec->stream_name_alt_analog);
+ if (!info)
+ return -ENOMEM;
+ spec->pcm_rec[2] = info;
+ if (spec->alt_dac_nid)
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+ &pcm_analog_alt_playback,
+ spec->stream_analog_alt_playback,
+ spec->alt_dac_nid);
+ else
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+ &pcm_null_stream, NULL, 0);
+ if (have_multi_adcs) {
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+ &pcm_analog_alt_capture,
+ spec->stream_analog_alt_capture,
+ spec->adc_nids[1]);
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
+ spec->num_adc_nids - 1;
+ } else {
+ setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+ &pcm_null_stream, NULL, 0);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_build_pcms);
+
+
+/*
+ * Standard auto-parser initializations
+ */
+
+/* configure the given path as a proper output */
+static void set_output_and_unmute(struct hda_codec *codec, int path_idx)
+{
+ struct nid_path *path;
+ hda_nid_t pin;
+
+ path = snd_hda_get_path_from_idx(codec, path_idx);
+ if (!path || !path->depth)
+ return;
+ pin = path->path[path->depth - 1];
+ restore_pin_ctl(codec, pin);
+ snd_hda_activate_path(codec, path, path->active,
+ aamix_default(codec->spec));
+ set_pin_eapd(codec, pin, path->active);
+}
+
+/* initialize primary output paths */
+static void init_multi_out(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < spec->autocfg.line_outs; i++)
+ set_output_and_unmute(codec, spec->out_paths[i]);
+}
+
+
+static void __init_extra_out(struct hda_codec *codec, int num_outs, int *paths)
+{
+ int i;
+
+ for (i = 0; i < num_outs; i++)
+ set_output_and_unmute(codec, paths[i]);
+}
+
+/* initialize hp and speaker paths */
+static void init_extra_out(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->autocfg.line_out_type != AUTO_PIN_HP_OUT)
+ __init_extra_out(codec, spec->autocfg.hp_outs, spec->hp_paths);
+ if (spec->autocfg.line_out_type != AUTO_PIN_SPEAKER_OUT)
+ __init_extra_out(codec, spec->autocfg.speaker_outs,
+ spec->speaker_paths);
+}
+
+/* initialize multi-io paths */
+static void init_multi_io(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < spec->multi_ios; i++) {
+ hda_nid_t pin = spec->multi_io[i].pin;
+ struct nid_path *path;
+ path = get_multiio_path(codec, i);
+ if (!path)
+ continue;
+ if (!spec->multi_io[i].ctl_in)
+ spec->multi_io[i].ctl_in =
+ snd_hda_codec_get_pin_target(codec, pin);
+ snd_hda_activate_path(codec, path, path->active,
+ aamix_default(spec));
+ }
+}
+
+static void init_aamix_paths(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!spec->have_aamix_ctl)
+ return;
+ if (!has_aamix_out_paths(spec))
+ return;
+ update_aamix_paths(codec, spec->aamix_mode, spec->out_paths[0],
+ spec->aamix_out_paths[0],
+ spec->autocfg.line_out_type);
+ update_aamix_paths(codec, spec->aamix_mode, spec->hp_paths[0],
+ spec->aamix_out_paths[1],
+ AUTO_PIN_HP_OUT);
+ update_aamix_paths(codec, spec->aamix_mode, spec->speaker_paths[0],
+ spec->aamix_out_paths[2],
+ AUTO_PIN_SPEAKER_OUT);
+}
+
+/* set up input pins and loopback paths */
+static void init_analog_input(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t nid = cfg->inputs[i].pin;
+ if (is_input_pin(codec, nid))
+ restore_pin_ctl(codec, nid);
+
+ /* init loopback inputs */
+ if (spec->mixer_nid) {
+ resume_path_from_idx(codec, spec->loopback_paths[i]);
+ resume_path_from_idx(codec, spec->loopback_merge_path);
+ }
+ }
+}
+
+/* initialize ADC paths */
+static void init_input_src(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct hda_input_mux *imux = &spec->input_mux;
+ struct nid_path *path;
+ int i, c, nums;
+
+ if (spec->dyn_adc_switch)
+ nums = 1;
+ else
+ nums = spec->num_adc_nids;
+
+ for (c = 0; c < nums; c++) {
+ for (i = 0; i < imux->num_items; i++) {
+ path = get_input_path(codec, c, i);
+ if (path) {
+ bool active = path->active;
+ if (i == spec->cur_mux[c])
+ active = true;
+ snd_hda_activate_path(codec, path, active, false);
+ }
+ }
+ if (spec->hp_mic)
+ update_hp_mic(codec, c, true);
+ }
+
+ if (spec->cap_sync_hook)
+ spec->cap_sync_hook(codec, NULL, NULL);
+}
+
+/* set right pin controls for digital I/O */
+static void init_digital(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+ hda_nid_t pin;
+
+ for (i = 0; i < spec->autocfg.dig_outs; i++)
+ set_output_and_unmute(codec, spec->digout_paths[i]);
+ pin = spec->autocfg.dig_in_pin;
+ if (pin) {
+ restore_pin_ctl(codec, pin);
+ resume_path_from_idx(codec, spec->digin_path);
+ }
+}
+
+/* clear unsol-event tags on unused pins; Conexant codecs seem to leave
+ * invalid unsol tags by some reason
+ */
+static void clear_unsol_on_unused_pins(struct hda_codec *codec)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ hda_nid_t nid = pin->nid;
+ if (is_jack_detectable(codec, nid) &&
+ !snd_hda_jack_tbl_get(codec, nid))
+ snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE, 0);
+ }
+}
+
+/**
+ * snd_hda_gen_init - initialize the generic spec
+ * @codec: the HDA codec
+ *
+ * This can be put as patch_ops init function.
+ */
+int snd_hda_gen_init(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->init_hook)
+ spec->init_hook(codec);
+
+ if (!spec->skip_verbs)
+ snd_hda_apply_verbs(codec);
+
+ init_multi_out(codec);
+ init_extra_out(codec);
+ init_multi_io(codec);
+ init_aamix_paths(codec);
+ init_analog_input(codec);
+ init_input_src(codec);
+ init_digital(codec);
+
+ clear_unsol_on_unused_pins(codec);
+
+ sync_all_pin_power_ctls(codec);
+
+ /* call init functions of standard auto-mute helpers */
+ update_automute_all(codec);
+
+ snd_hda_regmap_sync(codec);
+
+ if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook)
+ snd_hda_sync_vmaster_hook(&spec->vmaster_mute);
+
+ hda_call_check_power_status(codec, 0x01);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_init);
+
+/**
+ * snd_hda_gen_free - free the generic spec
+ * @codec: the HDA codec
+ *
+ * This can be put as patch_ops free function.
+ */
+void snd_hda_gen_free(struct hda_codec *codec)
+{
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_FREE);
+ snd_hda_gen_spec_free(codec->spec);
+ kfree(codec->spec);
+ codec->spec = NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_free);
+
+/**
+ * snd_hda_gen_reboot_notify - Make codec enter D3 before rebooting
+ * @codec: the HDA codec
+ *
+ * This can be put as patch_ops reboot_notify function.
+ */
+void snd_hda_gen_reboot_notify(struct hda_codec *codec)
+{
+ /* Make the codec enter D3 to avoid spurious noises from the internal
+ * speaker during (and after) reboot
+ */
+ snd_hda_codec_set_power_to_all(codec, codec->core.afg, AC_PWRST_D3);
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+ msleep(10);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_reboot_notify);
+
+#ifdef CONFIG_PM
+/**
+ * snd_hda_gen_check_power_status - check the loopback power save state
+ * @codec: the HDA codec
+ * @nid: NID to inspect
+ *
+ * This can be put as patch_ops check_power_status function.
+ */
+int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_check_power_status);
+#endif
+
+
+/*
+ * the generic codec support
+ */
+
+static const struct hda_codec_ops generic_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = snd_hda_gen_init,
+ .free = snd_hda_gen_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+ .reboot_notify = snd_hda_gen_reboot_notify,
+#ifdef CONFIG_PM
+ .check_power_status = snd_hda_gen_check_power_status,
+#endif
+};
+
+/*
+ * snd_hda_parse_generic_codec - Generic codec parser
+ * @codec: the HDA codec
+ */
+static int snd_hda_parse_generic_codec(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec;
+ int err;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ snd_hda_gen_spec_init(spec);
+ codec->spec = spec;
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->autocfg, NULL, 0);
+ if (err < 0)
+ goto error;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->autocfg);
+ if (err < 0)
+ goto error;
+
+ codec->patch_ops = generic_patch_ops;
+ return 0;
+
+error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+static const struct hda_device_id snd_hda_id_generic[] = {
+ HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC, "Generic", snd_hda_parse_generic_codec),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_generic);
+
+static struct hda_codec_driver generic_driver = {
+ .id = snd_hda_id_generic,
+};
+
+module_hda_codec_driver(generic_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic HD-audio codec parser");
diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h
new file mode 100644
index 000000000..578faa9ad
--- /dev/null
+++ b/sound/pci/hda/hda_generic.h
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Generic BIOS auto-parser helper functions for HD-audio
+ *
+ * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_GENERIC_H
+#define __SOUND_HDA_GENERIC_H
+
+#include <linux/leds.h>
+
+/* table entry for multi-io paths */
+struct hda_multi_io {
+ hda_nid_t pin; /* multi-io widget pin NID */
+ hda_nid_t dac; /* DAC to be connected */
+ unsigned int ctl_in; /* cached input-pin control value */
+};
+
+/* Widget connection path
+ *
+ * For output, stored in the order of DAC -> ... -> pin,
+ * for input, pin -> ... -> ADC.
+ *
+ * idx[i] contains the source index number to select on of the widget path[i];
+ * e.g. idx[1] is the index of the DAC (path[0]) selected by path[1] widget
+ * multi[] indicates whether it's a selector widget with multi-connectors
+ * (i.e. the connection selection is mandatory)
+ * vol_ctl and mute_ctl contains the NIDs for the assigned mixers
+ */
+
+#define MAX_NID_PATH_DEPTH 10
+
+enum {
+ NID_PATH_VOL_CTL,
+ NID_PATH_MUTE_CTL,
+ NID_PATH_BOOST_CTL,
+ NID_PATH_NUM_CTLS
+};
+
+struct nid_path {
+ int depth;
+ hda_nid_t path[MAX_NID_PATH_DEPTH];
+ unsigned char idx[MAX_NID_PATH_DEPTH];
+ unsigned char multi[MAX_NID_PATH_DEPTH];
+ unsigned int ctls[NID_PATH_NUM_CTLS]; /* NID_PATH_XXX_CTL */
+ bool active:1; /* activated by driver */
+ bool pin_enabled:1; /* pins are enabled */
+ bool pin_fixed:1; /* path with fixed pin */
+ bool stream_enabled:1; /* stream is active */
+};
+
+/* mic/line-in auto switching entry */
+
+#define MAX_AUTO_MIC_PINS 3
+
+struct automic_entry {
+ hda_nid_t pin; /* pin */
+ int idx; /* imux index, -1 = invalid */
+ unsigned int attr; /* pin attribute (INPUT_PIN_ATTR_*) */
+};
+
+/* active stream id */
+enum { STREAM_MULTI_OUT, STREAM_INDEP_HP };
+
+/* PCM hook action */
+enum {
+ HDA_GEN_PCM_ACT_OPEN,
+ HDA_GEN_PCM_ACT_PREPARE,
+ HDA_GEN_PCM_ACT_CLEANUP,
+ HDA_GEN_PCM_ACT_CLOSE,
+};
+
+/* DAC assignment badness table */
+struct badness_table {
+ int no_primary_dac; /* no primary DAC */
+ int no_dac; /* no secondary DACs */
+ int shared_primary; /* primary DAC is shared with main output */
+ int shared_surr; /* secondary DAC shared with main or primary */
+ int shared_clfe; /* third DAC shared with main or primary */
+ int shared_surr_main; /* secondary DAC sahred with main/DAC0 */
+};
+
+extern const struct badness_table hda_main_out_badness;
+extern const struct badness_table hda_extra_out_badness;
+
+struct hda_micmute_hook {
+ unsigned int led_mode;
+ unsigned int capture;
+ unsigned int led_value;
+ void (*old_hook)(struct hda_codec *codec,
+ struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+};
+
+struct hda_gen_spec {
+ char stream_name_analog[32]; /* analog PCM stream */
+ const struct hda_pcm_stream *stream_analog_playback;
+ const struct hda_pcm_stream *stream_analog_capture;
+
+ char stream_name_alt_analog[32]; /* alternative analog PCM stream */
+ const struct hda_pcm_stream *stream_analog_alt_playback;
+ const struct hda_pcm_stream *stream_analog_alt_capture;
+
+ char stream_name_digital[32]; /* digital PCM stream */
+ const struct hda_pcm_stream *stream_digital_playback;
+ const struct hda_pcm_stream *stream_digital_capture;
+
+ /* PCM */
+ unsigned int active_streams;
+ struct mutex pcm_mutex;
+
+ /* playback */
+ struct hda_multi_out multiout; /* playback set-up
+ * max_channels, dacs must be set
+ * dig_out_nid and hp_nid are optional
+ */
+ hda_nid_t alt_dac_nid;
+ hda_nid_t follower_dig_outs[3]; /* optional - for auto-parsing */
+ int dig_out_type;
+
+ /* capture */
+ unsigned int num_adc_nids;
+ hda_nid_t adc_nids[AUTO_CFG_MAX_INS];
+ hda_nid_t dig_in_nid; /* digital-in NID; optional */
+ hda_nid_t mixer_nid; /* analog-mixer NID */
+ hda_nid_t mixer_merge_nid; /* aamix merge-point NID (optional) */
+ const char *input_labels[HDA_MAX_NUM_INPUTS];
+ int input_label_idxs[HDA_MAX_NUM_INPUTS];
+
+ /* capture setup for dynamic dual-adc switch */
+ hda_nid_t cur_adc;
+ unsigned int cur_adc_stream_tag;
+ unsigned int cur_adc_format;
+
+ /* capture source */
+ struct hda_input_mux input_mux;
+ unsigned int cur_mux[3];
+
+ /* channel model */
+ /* min_channel_count contains the minimum channel count for primary
+ * outputs. When multi_ios is set, the channels can be configured
+ * between min_channel_count and (min_channel_count + multi_ios * 2).
+ *
+ * ext_channel_count contains the current channel count of the primary
+ * out. This varies in the range above.
+ *
+ * Meanwhile, const_channel_count is the channel count for all outputs
+ * including headphone and speakers. It's a constant value, and the
+ * PCM is set up as max(ext_channel_count, const_channel_count).
+ */
+ int min_channel_count; /* min. channel count for primary out */
+ int ext_channel_count; /* current channel count for primary */
+ int const_channel_count; /* channel count for all */
+
+ /* PCM information */
+ struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */
+
+ /* dynamic controls, init_verbs and input_mux */
+ struct auto_pin_cfg autocfg;
+ struct snd_array kctls;
+ hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
+ hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS];
+ unsigned int dyn_adc_idx[HDA_MAX_NUM_INPUTS];
+ /* shared hp/mic */
+ hda_nid_t shared_mic_vref_pin;
+ hda_nid_t hp_mic_pin;
+ int hp_mic_mux_idx;
+
+ /* DAC/ADC lists */
+ int num_all_dacs;
+ hda_nid_t all_dacs[16];
+ int num_all_adcs;
+ hda_nid_t all_adcs[AUTO_CFG_MAX_INS];
+
+ /* path list */
+ struct snd_array paths;
+
+ /* path indices */
+ int out_paths[AUTO_CFG_MAX_OUTS];
+ int hp_paths[AUTO_CFG_MAX_OUTS];
+ int speaker_paths[AUTO_CFG_MAX_OUTS];
+ int aamix_out_paths[3];
+ int digout_paths[AUTO_CFG_MAX_OUTS];
+ int input_paths[HDA_MAX_NUM_INPUTS][AUTO_CFG_MAX_INS];
+ int loopback_paths[HDA_MAX_NUM_INPUTS];
+ int loopback_merge_path;
+ int digin_path;
+
+ /* auto-mic stuff */
+ int am_num_entries;
+ struct automic_entry am_entry[MAX_AUTO_MIC_PINS];
+
+ /* for pin sensing */
+ /* current status; set in hda_geneic.c */
+ unsigned int hp_jack_present:1;
+ unsigned int line_jack_present:1;
+ unsigned int speaker_muted:1; /* current status of speaker mute */
+ unsigned int line_out_muted:1; /* current status of LO mute */
+
+ /* internal states of automute / autoswitch behavior */
+ unsigned int auto_mic:1;
+ unsigned int automute_speaker:1; /* automute speaker outputs */
+ unsigned int automute_lo:1; /* automute LO outputs */
+
+ /* capabilities detected by parser */
+ unsigned int detect_hp:1; /* Headphone detection enabled */
+ unsigned int detect_lo:1; /* Line-out detection enabled */
+ unsigned int automute_speaker_possible:1; /* there are speakers and either LO or HP */
+ unsigned int automute_lo_possible:1; /* there are line outs and HP */
+
+ /* additional parameters set by codec drivers */
+ unsigned int master_mute:1; /* master mute over all */
+ unsigned int keep_vref_in_automute:1; /* Don't clear VREF in automute */
+ unsigned int line_in_auto_switch:1; /* allow line-in auto switch */
+ unsigned int auto_mute_via_amp:1; /* auto-mute via amp instead of pinctl */
+
+ /* parser behavior flags; set before snd_hda_gen_parse_auto_config() */
+ unsigned int suppress_auto_mute:1; /* suppress input jack auto mute */
+ unsigned int suppress_auto_mic:1; /* suppress input jack auto switch */
+
+ /* other parse behavior flags */
+ unsigned int need_dac_fix:1; /* need to limit DACs for multi channels */
+ unsigned int hp_mic:1; /* Allow HP as a mic-in */
+ unsigned int suppress_hp_mic_detect:1; /* Don't detect HP/mic */
+ unsigned int no_primary_hp:1; /* Don't prefer HP pins to speaker pins */
+ unsigned int no_multi_io:1; /* Don't try multi I/O config */
+ unsigned int multi_cap_vol:1; /* allow multiple capture xxx volumes */
+ unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */
+ unsigned int own_eapd_ctl:1; /* set EAPD by own function */
+ unsigned int keep_eapd_on:1; /* don't turn off EAPD automatically */
+ unsigned int vmaster_mute_enum:1; /* add vmaster mute mode enum */
+ unsigned int indep_hp:1; /* independent HP supported */
+ unsigned int prefer_hp_amp:1; /* enable HP amp for speaker if any */
+ unsigned int add_stereo_mix_input:2; /* add aamix as a capture src */
+ unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */
+ unsigned int power_down_unused:1; /* power down unused widgets */
+ unsigned int dac_min_mute:1; /* minimal = mute for DACs */
+ unsigned int suppress_vmaster:1; /* don't create vmaster kctls */
+ unsigned int obey_preferred_dacs:1; /* obey preferred_dacs assignment */
+
+ /* other internal flags */
+ unsigned int no_analog:1; /* digital I/O only */
+ unsigned int dyn_adc_switch:1; /* switch ADCs (for ALC275) */
+ unsigned int indep_hp_enabled:1; /* independent HP enabled */
+ unsigned int have_aamix_ctl:1;
+ unsigned int hp_mic_jack_modes:1;
+ unsigned int skip_verbs:1; /* don't apply verbs at snd_hda_gen_init() */
+
+ /* additional mute flags (only effective with auto_mute_via_amp=1) */
+ u64 mute_bits;
+
+ /* bitmask for skipping volume controls */
+ u64 out_vol_mask;
+
+ /* badness tables for output path evaluations */
+ const struct badness_table *main_out_badness;
+ const struct badness_table *extra_out_badness;
+
+ /* preferred pin/DAC pairs; an array of paired NIDs */
+ const hda_nid_t *preferred_dacs;
+
+ /* loopback mixing mode */
+ bool aamix_mode;
+
+ /* digital beep */
+ hda_nid_t beep_nid;
+
+ /* for virtual master */
+ hda_nid_t vmaster_nid;
+ unsigned int vmaster_tlv[4];
+ struct hda_vmaster_mute_hook vmaster_mute;
+
+ struct hda_loopback_check loopback;
+ struct snd_array loopback_list;
+
+ /* multi-io */
+ int multi_ios;
+ struct hda_multi_io multi_io[4];
+
+ /* hooks */
+ void (*init_hook)(struct hda_codec *codec);
+ void (*automute_hook)(struct hda_codec *codec);
+ void (*cap_sync_hook)(struct hda_codec *codec,
+ struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+ /* mic mute LED hook; called via cap_sync_hook */
+ struct hda_micmute_hook micmute_led;
+
+ /* PCM hooks */
+ void (*pcm_playback_hook)(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action);
+ void (*pcm_capture_hook)(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action);
+
+ /* automute / autoswitch hooks */
+ void (*hp_automute_hook)(struct hda_codec *codec,
+ struct hda_jack_callback *cb);
+ void (*line_automute_hook)(struct hda_codec *codec,
+ struct hda_jack_callback *cb);
+ void (*mic_autoswitch_hook)(struct hda_codec *codec,
+ struct hda_jack_callback *cb);
+
+ /* leds */
+ struct led_classdev *led_cdevs[NUM_AUDIO_LEDS];
+};
+
+/* values for add_stereo_mix_input flag */
+enum {
+ HDA_HINT_STEREO_MIX_DISABLE, /* No stereo mix input */
+ HDA_HINT_STEREO_MIX_ENABLE, /* Add stereo mix input */
+ HDA_HINT_STEREO_MIX_AUTO, /* Add only if auto-mic is disabled */
+};
+
+int snd_hda_gen_spec_init(struct hda_gen_spec *spec);
+
+int snd_hda_gen_init(struct hda_codec *codec);
+void snd_hda_gen_free(struct hda_codec *codec);
+
+int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path);
+struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx);
+struct nid_path *
+snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid,
+ hda_nid_t to_nid, int anchor_nid);
+void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path,
+ bool enable, bool add_aamix);
+
+struct snd_kcontrol_new *
+snd_hda_gen_add_kctl(struct hda_gen_spec *spec, const char *name,
+ const struct snd_kcontrol_new *temp);
+
+int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg);
+int snd_hda_gen_build_controls(struct hda_codec *codec);
+int snd_hda_gen_build_pcms(struct hda_codec *codec);
+void snd_hda_gen_reboot_notify(struct hda_codec *codec);
+
+/* standard jack event callbacks */
+void snd_hda_gen_hp_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack);
+void snd_hda_gen_line_automute(struct hda_codec *codec,
+ struct hda_jack_callback *jack);
+void snd_hda_gen_mic_autoswitch(struct hda_codec *codec,
+ struct hda_jack_callback *jack);
+void snd_hda_gen_update_outputs(struct hda_codec *codec);
+
+#ifdef CONFIG_PM
+int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid);
+#endif
+unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state);
+void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on);
+int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin);
+
+int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec,
+ int (*callback)(struct led_classdev *,
+ enum led_brightness));
+int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec,
+ int (*callback)(struct led_classdev *,
+ enum led_brightness));
+
+#endif /* __SOUND_HDA_GENERIC_H */
diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c
new file mode 100644
index 000000000..125e97fe0
--- /dev/null
+++ b/sound/pci/hda/hda_hwdep.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HWDEP Interface for HD-audio codec
+ *
+ * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+#include <linux/nospec.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include <sound/hda_hwdep.h>
+#include <sound/minors.h>
+
+/*
+ * write/read an out-of-bound verb
+ */
+static int verb_write_ioctl(struct hda_codec *codec,
+ struct hda_verb_ioctl __user *arg)
+{
+ u32 verb, res;
+
+ if (get_user(verb, &arg->verb))
+ return -EFAULT;
+ res = snd_hda_codec_read(codec, verb >> 24, 0,
+ (verb >> 8) & 0xffff, verb & 0xff);
+ if (put_user(res, &arg->res))
+ return -EFAULT;
+ return 0;
+}
+
+static int get_wcap_ioctl(struct hda_codec *codec,
+ struct hda_verb_ioctl __user *arg)
+{
+ u32 verb, res;
+
+ if (get_user(verb, &arg->verb))
+ return -EFAULT;
+ /* open-code get_wcaps(verb>>24) with nospec */
+ verb >>= 24;
+ if (verb < codec->core.start_nid ||
+ verb >= codec->core.start_nid + codec->core.num_nodes) {
+ res = 0;
+ } else {
+ verb -= codec->core.start_nid;
+ verb = array_index_nospec(verb, codec->core.num_nodes);
+ res = codec->wcaps[verb];
+ }
+ if (put_user(res, &arg->res))
+ return -EFAULT;
+ return 0;
+}
+
+
+/*
+ */
+static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct hda_codec *codec = hw->private_data;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case HDA_IOCTL_PVERSION:
+ return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
+ case HDA_IOCTL_VERB_WRITE:
+ return verb_write_ioctl(codec, argp);
+ case HDA_IOCTL_GET_WCAP:
+ return get_wcap_ioctl(codec, argp);
+ }
+ return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_COMPAT
+static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+#ifndef CONFIG_SND_DEBUG_VERBOSE
+ if (!capable(CAP_SYS_RAWIO))
+ return -EACCES;
+#endif
+ return 0;
+}
+
+int snd_hda_create_hwdep(struct hda_codec *codec)
+{
+ char hwname[16];
+ struct snd_hwdep *hwdep;
+ int err;
+
+ sprintf(hwname, "HDA Codec %d", codec->addr);
+ err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep);
+ if (err < 0)
+ return err;
+ codec->hwdep = hwdep;
+ sprintf(hwdep->name, "HDA Codec %d", codec->addr);
+ hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
+ hwdep->private_data = codec;
+ hwdep->exclusive = 1;
+
+ hwdep->ops.open = hda_hwdep_open;
+ hwdep->ops.ioctl = hda_hwdep_ioctl;
+#ifdef CONFIG_COMPAT
+ hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
+#endif
+
+ /* for sysfs */
+ hwdep->dev.groups = snd_hda_dev_attr_groups;
+ dev_set_drvdata(&hwdep->dev, codec);
+
+ return 0;
+}
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
new file mode 100644
index 000000000..12c6eb76f
--- /dev/null
+++ b/sound/pci/hda/hda_intel.c
@@ -0,0 +1,2860 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * hda_intel.c - Implementation of primary alsa driver code base
+ * for Intel HD Audio.
+ *
+ * Copyright(c) 2004 Intel Corporation. All rights reserved.
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ * PeiSen Hou <pshou@realtek.com.tw>
+ *
+ * CONTACTS:
+ *
+ * Matt Jared matt.jared@intel.com
+ * Andy Kopp andy.kopp@intel.com
+ * Dan Kogan dan.d.kogan@intel.com
+ *
+ * CHANGES:
+ *
+ * 2004.12.01 Major rewrite by tiwai, merged the work of pshou
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/clocksource.h>
+#include <linux/time.h>
+#include <linux/completion.h>
+#include <linux/acpi.h>
+#include <linux/pgtable.h>
+
+#ifdef CONFIG_X86
+/* for snoop control */
+#include <asm/set_memory.h>
+#include <asm/cpufeature.h>
+#endif
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
+#include <sound/intel-dsp-config.h>
+#include <linux/vgaarb.h>
+#include <linux/vga_switcheroo.h>
+#include <linux/firmware.h>
+#include <sound/hda_codec.h>
+#include "hda_controller.h"
+#include "hda_intel.h"
+
+#define CREATE_TRACE_POINTS
+#include "hda_intel_trace.h"
+
+/* position fix mode */
+enum {
+ POS_FIX_AUTO,
+ POS_FIX_LPIB,
+ POS_FIX_POSBUF,
+ POS_FIX_VIACOMBO,
+ POS_FIX_COMBO,
+ POS_FIX_SKL,
+ POS_FIX_FIFO,
+};
+
+/* Defines for ATI HD Audio support in SB450 south bridge */
+#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR 0x42
+#define ATI_SB450_HDAUDIO_ENABLE_SNOOP 0x02
+
+/* Defines for Nvidia HDA support */
+#define NVIDIA_HDA_TRANSREG_ADDR 0x4e
+#define NVIDIA_HDA_ENABLE_COHBITS 0x0f
+#define NVIDIA_HDA_ISTRM_COH 0x4d
+#define NVIDIA_HDA_OSTRM_COH 0x4c
+#define NVIDIA_HDA_ENABLE_COHBIT 0x01
+
+/* Defines for Intel SCH HDA snoop control */
+#define INTEL_HDA_CGCTL 0x48
+#define INTEL_HDA_CGCTL_MISCBDCGE (0x1 << 6)
+#define INTEL_SCH_HDA_DEVC 0x78
+#define INTEL_SCH_HDA_DEVC_NOSNOOP (0x1<<11)
+
+/* Define VIA HD Audio Device ID*/
+#define VIA_HDAC_DEVICE_ID 0x3288
+
+/* max number of SDs */
+/* ICH, ATI and VIA have 4 playback and 4 capture */
+#define ICH6_NUM_CAPTURE 4
+#define ICH6_NUM_PLAYBACK 4
+
+/* ULI has 6 playback and 5 capture */
+#define ULI_NUM_CAPTURE 5
+#define ULI_NUM_PLAYBACK 6
+
+/* ATI HDMI may have up to 8 playbacks and 0 capture */
+#define ATIHDMI_NUM_CAPTURE 0
+#define ATIHDMI_NUM_PLAYBACK 8
+
+/* TERA has 4 playback and 3 capture */
+#define TERA_NUM_CAPTURE 3
+#define TERA_NUM_PLAYBACK 4
+
+
+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 char *model[SNDRV_CARDS];
+static int position_fix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
+static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
+static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
+static int probe_only[SNDRV_CARDS];
+static int jackpoll_ms[SNDRV_CARDS];
+static int single_cmd = -1;
+static int enable_msi = -1;
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+static char *patch[SNDRV_CARDS];
+#endif
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+static bool beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] =
+ CONFIG_SND_HDA_INPUT_BEEP_MODE};
+#endif
+static bool dmic_detect = 1;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Intel HD audio interface.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Intel HD audio interface.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+module_param_array(position_fix, int, NULL, 0444);
+MODULE_PARM_DESC(position_fix, "DMA pointer read method."
+ "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+, 6 = FIFO).");
+module_param_array(bdl_pos_adj, int, NULL, 0644);
+MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset.");
+module_param_array(probe_mask, int, NULL, 0444);
+MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1).");
+module_param_array(probe_only, int, NULL, 0444);
+MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization.");
+module_param_array(jackpoll_ms, int, NULL, 0444);
+MODULE_PARM_DESC(jackpoll_ms, "Ms between polling for jack events (default = 0, using unsol events only)");
+module_param(single_cmd, bint, 0444);
+MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
+ "(for debugging only).");
+module_param(enable_msi, bint, 0444);
+MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)");
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+module_param_array(patch, charp, NULL, 0444);
+MODULE_PARM_DESC(patch, "Patch file for Intel HD audio interface.");
+#endif
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+module_param_array(beep_mode, bool, NULL, 0444);
+MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode "
+ "(0=off, 1=on) (default=1).");
+#endif
+module_param(dmic_detect, bool, 0444);
+MODULE_PARM_DESC(dmic_detect, "Allow DSP driver selection (bypass this driver) "
+ "(0=off, 1=on) (default=1); "
+ "deprecated, use snd-intel-dspcfg.dsp_driver option instead");
+
+#ifdef CONFIG_PM
+static int param_set_xint(const char *val, const struct kernel_param *kp);
+static const struct kernel_param_ops param_ops_xint = {
+ .set = param_set_xint,
+ .get = param_get_int,
+};
+#define param_check_xint param_check_int
+
+static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
+module_param(power_save, xint, 0644);
+MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
+ "(in second, 0 = disable).");
+
+static bool pm_blacklist = true;
+module_param(pm_blacklist, bool, 0644);
+MODULE_PARM_DESC(pm_blacklist, "Enable power-management denylist");
+
+/* reset the HD-audio controller in power save mode.
+ * this may give more power-saving, but will take longer time to
+ * wake up.
+ */
+static bool power_save_controller = 1;
+module_param(power_save_controller, bool, 0644);
+MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode.");
+#else
+#define power_save 0
+#endif /* CONFIG_PM */
+
+static int align_buffer_size = -1;
+module_param(align_buffer_size, bint, 0644);
+MODULE_PARM_DESC(align_buffer_size,
+ "Force buffer and period sizes to be multiple of 128 bytes.");
+
+#ifdef CONFIG_X86
+static int hda_snoop = -1;
+module_param_named(snoop, hda_snoop, bint, 0444);
+MODULE_PARM_DESC(snoop, "Enable/disable snooping");
+#else
+#define hda_snoop true
+#endif
+
+
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel, ICH6},"
+ "{Intel, ICH6M},"
+ "{Intel, ICH7},"
+ "{Intel, ESB2},"
+ "{Intel, ICH8},"
+ "{Intel, ICH9},"
+ "{Intel, ICH10},"
+ "{Intel, PCH},"
+ "{Intel, CPT},"
+ "{Intel, PPT},"
+ "{Intel, LPT},"
+ "{Intel, LPT_LP},"
+ "{Intel, WPT_LP},"
+ "{Intel, SPT},"
+ "{Intel, SPT_LP},"
+ "{Intel, HPT},"
+ "{Intel, PBG},"
+ "{Intel, SCH},"
+ "{ATI, SB450},"
+ "{ATI, SB600},"
+ "{ATI, RS600},"
+ "{ATI, RS690},"
+ "{ATI, RS780},"
+ "{ATI, R600},"
+ "{ATI, RV630},"
+ "{ATI, RV610},"
+ "{ATI, RV670},"
+ "{ATI, RV635},"
+ "{ATI, RV620},"
+ "{ATI, RV770},"
+ "{VIA, VT8251},"
+ "{VIA, VT8237A},"
+ "{SiS, SIS966},"
+ "{ULI, M5461}}");
+MODULE_DESCRIPTION("Intel HDA driver");
+
+#if defined(CONFIG_PM) && defined(CONFIG_VGA_SWITCHEROO)
+#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
+#define SUPPORT_VGA_SWITCHEROO
+#endif
+#endif
+
+
+/*
+ */
+
+/* driver types */
+enum {
+ AZX_DRIVER_ICH,
+ AZX_DRIVER_PCH,
+ AZX_DRIVER_SCH,
+ AZX_DRIVER_SKL,
+ AZX_DRIVER_HDMI,
+ AZX_DRIVER_ATI,
+ AZX_DRIVER_ATIHDMI,
+ AZX_DRIVER_ATIHDMI_NS,
+ AZX_DRIVER_GFHDMI,
+ AZX_DRIVER_VIA,
+ AZX_DRIVER_SIS,
+ AZX_DRIVER_ULI,
+ AZX_DRIVER_NVIDIA,
+ AZX_DRIVER_TERA,
+ AZX_DRIVER_CTX,
+ AZX_DRIVER_CTHDA,
+ AZX_DRIVER_CMEDIA,
+ AZX_DRIVER_ZHAOXIN,
+ AZX_DRIVER_GENERIC,
+ AZX_NUM_DRIVERS, /* keep this as last entry */
+};
+
+#define azx_get_snoop_type(chip) \
+ (((chip)->driver_caps & AZX_DCAPS_SNOOP_MASK) >> 10)
+#define AZX_DCAPS_SNOOP_TYPE(type) ((AZX_SNOOP_TYPE_ ## type) << 10)
+
+/* quirks for old Intel chipsets */
+#define AZX_DCAPS_INTEL_ICH \
+ (AZX_DCAPS_OLD_SSYNC | AZX_DCAPS_NO_ALIGN_BUFSIZE)
+
+/* quirks for Intel PCH */
+#define AZX_DCAPS_INTEL_PCH_BASE \
+ (AZX_DCAPS_NO_ALIGN_BUFSIZE | AZX_DCAPS_COUNT_LPIB_DELAY |\
+ AZX_DCAPS_SNOOP_TYPE(SCH))
+
+/* PCH up to IVB; no runtime PM; bind with i915 gfx */
+#define AZX_DCAPS_INTEL_PCH_NOPM \
+ (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_I915_COMPONENT)
+
+/* PCH for HSW/BDW; with runtime PM */
+/* no i915 binding for this as HSW/BDW has another controller for HDMI */
+#define AZX_DCAPS_INTEL_PCH \
+ (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME)
+
+/* HSW HDMI */
+#define AZX_DCAPS_INTEL_HASWELL \
+ (/*AZX_DCAPS_ALIGN_BUFSIZE |*/ AZX_DCAPS_COUNT_LPIB_DELAY |\
+ AZX_DCAPS_PM_RUNTIME | AZX_DCAPS_I915_COMPONENT |\
+ AZX_DCAPS_SNOOP_TYPE(SCH))
+
+/* Broadwell HDMI can't use position buffer reliably, force to use LPIB */
+#define AZX_DCAPS_INTEL_BROADWELL \
+ (/*AZX_DCAPS_ALIGN_BUFSIZE |*/ AZX_DCAPS_POSFIX_LPIB |\
+ AZX_DCAPS_PM_RUNTIME | AZX_DCAPS_I915_COMPONENT |\
+ AZX_DCAPS_SNOOP_TYPE(SCH))
+
+#define AZX_DCAPS_INTEL_BAYTRAIL \
+ (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_I915_COMPONENT)
+
+#define AZX_DCAPS_INTEL_BRASWELL \
+ (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME |\
+ AZX_DCAPS_I915_COMPONENT)
+
+#define AZX_DCAPS_INTEL_SKYLAKE \
+ (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME |\
+ AZX_DCAPS_SEPARATE_STREAM_TAG | AZX_DCAPS_I915_COMPONENT)
+
+#define AZX_DCAPS_INTEL_BROXTON AZX_DCAPS_INTEL_SKYLAKE
+
+/* quirks for ATI SB / AMD Hudson */
+#define AZX_DCAPS_PRESET_ATI_SB \
+ (AZX_DCAPS_NO_TCSEL | AZX_DCAPS_POSFIX_LPIB |\
+ AZX_DCAPS_SNOOP_TYPE(ATI))
+
+/* quirks for ATI/AMD HDMI */
+#define AZX_DCAPS_PRESET_ATI_HDMI \
+ (AZX_DCAPS_NO_TCSEL | AZX_DCAPS_POSFIX_LPIB|\
+ AZX_DCAPS_NO_MSI64)
+
+/* quirks for ATI HDMI with snoop off */
+#define AZX_DCAPS_PRESET_ATI_HDMI_NS \
+ (AZX_DCAPS_PRESET_ATI_HDMI | AZX_DCAPS_SNOOP_OFF)
+
+/* quirks for AMD SB */
+#define AZX_DCAPS_PRESET_AMD_SB \
+ (AZX_DCAPS_NO_TCSEL | AZX_DCAPS_AMD_WORKAROUND |\
+ AZX_DCAPS_SNOOP_TYPE(ATI) | AZX_DCAPS_PM_RUNTIME |\
+ AZX_DCAPS_RETRY_PROBE)
+
+/* quirks for Nvidia */
+#define AZX_DCAPS_PRESET_NVIDIA \
+ (AZX_DCAPS_NO_MSI | AZX_DCAPS_CORBRP_SELF_CLEAR |\
+ AZX_DCAPS_SNOOP_TYPE(NVIDIA))
+
+#define AZX_DCAPS_PRESET_CTHDA \
+ (AZX_DCAPS_NO_MSI | AZX_DCAPS_POSFIX_LPIB |\
+ AZX_DCAPS_NO_64BIT |\
+ AZX_DCAPS_4K_BDLE_BOUNDARY | AZX_DCAPS_SNOOP_OFF)
+
+/*
+ * vga_switcheroo support
+ */
+#ifdef SUPPORT_VGA_SWITCHEROO
+#define use_vga_switcheroo(chip) ((chip)->use_vga_switcheroo)
+#define needs_eld_notify_link(chip) ((chip)->bus.keep_power)
+#else
+#define use_vga_switcheroo(chip) 0
+#define needs_eld_notify_link(chip) false
+#endif
+
+#define CONTROLLER_IN_GPU(pci) (((pci)->vendor == 0x8086) && \
+ (((pci)->device == 0x0a0c) || \
+ ((pci)->device == 0x0c0c) || \
+ ((pci)->device == 0x0d0c) || \
+ ((pci)->device == 0x160c) || \
+ ((pci)->device == 0x490d) || \
+ ((pci)->device == 0x4f90) || \
+ ((pci)->device == 0x4f91) || \
+ ((pci)->device == 0x4f92)))
+
+#define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98)
+
+static const char * const driver_short_names[] = {
+ [AZX_DRIVER_ICH] = "HDA Intel",
+ [AZX_DRIVER_PCH] = "HDA Intel PCH",
+ [AZX_DRIVER_SCH] = "HDA Intel MID",
+ [AZX_DRIVER_SKL] = "HDA Intel PCH", /* kept old name for compatibility */
+ [AZX_DRIVER_HDMI] = "HDA Intel HDMI",
+ [AZX_DRIVER_ATI] = "HDA ATI SB",
+ [AZX_DRIVER_ATIHDMI] = "HDA ATI HDMI",
+ [AZX_DRIVER_ATIHDMI_NS] = "HDA ATI HDMI",
+ [AZX_DRIVER_GFHDMI] = "HDA GF HDMI",
+ [AZX_DRIVER_VIA] = "HDA VIA VT82xx",
+ [AZX_DRIVER_SIS] = "HDA SIS966",
+ [AZX_DRIVER_ULI] = "HDA ULI M5461",
+ [AZX_DRIVER_NVIDIA] = "HDA NVidia",
+ [AZX_DRIVER_TERA] = "HDA Teradici",
+ [AZX_DRIVER_CTX] = "HDA Creative",
+ [AZX_DRIVER_CTHDA] = "HDA Creative",
+ [AZX_DRIVER_CMEDIA] = "HDA C-Media",
+ [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin",
+ [AZX_DRIVER_GENERIC] = "HD-Audio Generic",
+};
+
+static int azx_acquire_irq(struct azx *chip, int do_disconnect);
+static void set_default_power_save(struct azx *chip);
+
+/*
+ * initialize the PCI registers
+ */
+/* update bits in a PCI register byte */
+static void update_pci_byte(struct pci_dev *pci, unsigned int reg,
+ unsigned char mask, unsigned char val)
+{
+ unsigned char data;
+
+ pci_read_config_byte(pci, reg, &data);
+ data &= ~mask;
+ data |= (val & mask);
+ pci_write_config_byte(pci, reg, data);
+}
+
+static void azx_init_pci(struct azx *chip)
+{
+ int snoop_type = azx_get_snoop_type(chip);
+
+ /* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
+ * TCSEL == Traffic Class Select Register, which sets PCI express QOS
+ * Ensuring these bits are 0 clears playback static on some HD Audio
+ * codecs.
+ * The PCI register TCSEL is defined in the Intel manuals.
+ */
+ if (!(chip->driver_caps & AZX_DCAPS_NO_TCSEL)) {
+ dev_dbg(chip->card->dev, "Clearing TCSEL\n");
+ update_pci_byte(chip->pci, AZX_PCIREG_TCSEL, 0x07, 0);
+ }
+
+ /* For ATI SB450/600/700/800/900 and AMD Hudson azalia HD audio,
+ * we need to enable snoop.
+ */
+ if (snoop_type == AZX_SNOOP_TYPE_ATI) {
+ dev_dbg(chip->card->dev, "Setting ATI snoop: %d\n",
+ azx_snoop(chip));
+ update_pci_byte(chip->pci,
+ ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, 0x07,
+ azx_snoop(chip) ? ATI_SB450_HDAUDIO_ENABLE_SNOOP : 0);
+ }
+
+ /* For NVIDIA HDA, enable snoop */
+ if (snoop_type == AZX_SNOOP_TYPE_NVIDIA) {
+ dev_dbg(chip->card->dev, "Setting Nvidia snoop: %d\n",
+ azx_snoop(chip));
+ update_pci_byte(chip->pci,
+ NVIDIA_HDA_TRANSREG_ADDR,
+ 0x0f, NVIDIA_HDA_ENABLE_COHBITS);
+ update_pci_byte(chip->pci,
+ NVIDIA_HDA_ISTRM_COH,
+ 0x01, NVIDIA_HDA_ENABLE_COHBIT);
+ update_pci_byte(chip->pci,
+ NVIDIA_HDA_OSTRM_COH,
+ 0x01, NVIDIA_HDA_ENABLE_COHBIT);
+ }
+
+ /* Enable SCH/PCH snoop if needed */
+ if (snoop_type == AZX_SNOOP_TYPE_SCH) {
+ unsigned short snoop;
+ pci_read_config_word(chip->pci, INTEL_SCH_HDA_DEVC, &snoop);
+ if ((!azx_snoop(chip) && !(snoop & INTEL_SCH_HDA_DEVC_NOSNOOP)) ||
+ (azx_snoop(chip) && (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP))) {
+ snoop &= ~INTEL_SCH_HDA_DEVC_NOSNOOP;
+ if (!azx_snoop(chip))
+ snoop |= INTEL_SCH_HDA_DEVC_NOSNOOP;
+ pci_write_config_word(chip->pci, INTEL_SCH_HDA_DEVC, snoop);
+ pci_read_config_word(chip->pci,
+ INTEL_SCH_HDA_DEVC, &snoop);
+ }
+ dev_dbg(chip->card->dev, "SCH snoop: %s\n",
+ (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) ?
+ "Disabled" : "Enabled");
+ }
+}
+
+/*
+ * In BXT-P A0, HD-Audio DMA requests is later than expected,
+ * and makes an audio stream sensitive to system latencies when
+ * 24/32 bits are playing.
+ * Adjusting threshold of DMA fifo to force the DMA request
+ * sooner to improve latency tolerance at the expense of power.
+ */
+static void bxt_reduce_dma_latency(struct azx *chip)
+{
+ u32 val;
+
+ val = azx_readl(chip, VS_EM4L);
+ val &= (0x3 << 20);
+ azx_writel(chip, VS_EM4L, val);
+}
+
+/*
+ * ML_LCAP bits:
+ * bit 0: 6 MHz Supported
+ * bit 1: 12 MHz Supported
+ * bit 2: 24 MHz Supported
+ * bit 3: 48 MHz Supported
+ * bit 4: 96 MHz Supported
+ * bit 5: 192 MHz Supported
+ */
+static int intel_get_lctl_scf(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ static const int preferred_bits[] = { 2, 3, 1, 4, 5 };
+ u32 val, t;
+ int i;
+
+ val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP);
+
+ for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) {
+ t = preferred_bits[i];
+ if (val & (1 << t))
+ return t;
+ }
+
+ dev_warn(chip->card->dev, "set audio clock frequency to 6MHz");
+ return 0;
+}
+
+static int intel_ml_lctl_set_power(struct azx *chip, int state)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ u32 val;
+ int timeout;
+
+ /*
+ * the codecs are sharing the first link setting by default
+ * If other links are enabled for stream, they need similar fix
+ */
+ val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+ val &= ~AZX_MLCTL_SPA;
+ val |= state << AZX_MLCTL_SPA_SHIFT;
+ writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+ /* wait for CPA */
+ timeout = 50;
+ while (timeout) {
+ if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) &
+ AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT))
+ return 0;
+ timeout--;
+ udelay(10);
+ }
+
+ return -1;
+}
+
+static void intel_init_lctl(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ u32 val;
+ int ret;
+
+ /* 0. check lctl register value is correct or not */
+ val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+ /* if SCF is already set, let's use it */
+ if ((val & ML_LCTL_SCF_MASK) != 0)
+ return;
+
+ /*
+ * Before operating on SPA, CPA must match SPA.
+ * Any deviation may result in undefined behavior.
+ */
+ if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) !=
+ ((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT))
+ return;
+
+ /* 1. turn link down: set SPA to 0 and wait CPA to 0 */
+ ret = intel_ml_lctl_set_power(chip, 0);
+ udelay(100);
+ if (ret)
+ goto set_spa;
+
+ /* 2. update SCF to select a properly audio clock*/
+ val &= ~ML_LCTL_SCF_MASK;
+ val |= intel_get_lctl_scf(chip);
+ writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+
+set_spa:
+ /* 4. turn link up: set SPA to 1 and wait CPA to 1 */
+ intel_ml_lctl_set_power(chip, 1);
+ udelay(100);
+}
+
+static void hda_intel_init_chip(struct azx *chip, bool full_reset)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct pci_dev *pci = chip->pci;
+ u32 val;
+
+ snd_hdac_set_codec_wakeup(bus, true);
+ if (chip->driver_type == AZX_DRIVER_SKL) {
+ pci_read_config_dword(pci, INTEL_HDA_CGCTL, &val);
+ val = val & ~INTEL_HDA_CGCTL_MISCBDCGE;
+ pci_write_config_dword(pci, INTEL_HDA_CGCTL, val);
+ }
+ azx_init_chip(chip, full_reset);
+ if (chip->driver_type == AZX_DRIVER_SKL) {
+ pci_read_config_dword(pci, INTEL_HDA_CGCTL, &val);
+ val = val | INTEL_HDA_CGCTL_MISCBDCGE;
+ pci_write_config_dword(pci, INTEL_HDA_CGCTL, val);
+ }
+
+ snd_hdac_set_codec_wakeup(bus, false);
+
+ /* reduce dma latency to avoid noise */
+ if (IS_BXT(pci))
+ bxt_reduce_dma_latency(chip);
+
+ if (bus->mlcap != NULL)
+ intel_init_lctl(chip);
+}
+
+/* calculate runtime delay from LPIB */
+static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev,
+ unsigned int pos)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ int stream = substream->stream;
+ unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev);
+ int delay;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ delay = pos - lpib_pos;
+ else
+ delay = lpib_pos - pos;
+ if (delay < 0) {
+ if (delay >= azx_dev->core.delay_negative_threshold)
+ delay = 0;
+ else
+ delay += azx_dev->core.bufsize;
+ }
+
+ if (delay >= azx_dev->core.period_bytes) {
+ dev_info(chip->card->dev,
+ "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n",
+ delay, azx_dev->core.period_bytes);
+ delay = 0;
+ chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY;
+ chip->get_delay[stream] = NULL;
+ }
+
+ return bytes_to_frames(substream->runtime, delay);
+}
+
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev);
+
+/* called from IRQ */
+static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ int ok;
+
+ ok = azx_position_ok(chip, azx_dev);
+ if (ok == 1) {
+ azx_dev->irq_pending = 0;
+ return ok;
+ } else if (ok == 0) {
+ /* bogus IRQ, process it later */
+ azx_dev->irq_pending = 1;
+ schedule_work(&hda->irq_pending_work);
+ }
+ return 0;
+}
+
+#define display_power(chip, enable) \
+ snd_hdac_display_power(azx_bus(chip), HDA_CODEC_IDX_CONTROLLER, enable)
+
+/*
+ * Check whether the current DMA position is acceptable for updating
+ * periods. Returns non-zero if it's OK.
+ *
+ * Many HD-audio controllers appear pretty inaccurate about
+ * the update-IRQ timing. The IRQ is issued before actually the
+ * data is processed. So, we need to process it afterwords in a
+ * workqueue.
+ *
+ * Returns 1 if OK to proceed, 0 for delay handling, -1 for skipping update
+ */
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int stream = substream->stream;
+ u32 wallclk;
+ unsigned int pos;
+ snd_pcm_uframes_t hwptr, target;
+
+ wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk;
+ if (wallclk < (azx_dev->core.period_wallclk * 2) / 3)
+ return -1; /* bogus (too early) interrupt */
+
+ if (chip->get_position[stream])
+ pos = chip->get_position[stream](chip, azx_dev);
+ else { /* use the position buffer as default */
+ pos = azx_get_pos_posbuf(chip, azx_dev);
+ if (!pos || pos == (u32)-1) {
+ dev_info(chip->card->dev,
+ "Invalid position buffer, using LPIB read method instead.\n");
+ chip->get_position[stream] = azx_get_pos_lpib;
+ if (chip->get_position[0] == azx_get_pos_lpib &&
+ chip->get_position[1] == azx_get_pos_lpib)
+ azx_bus(chip)->use_posbuf = false;
+ pos = azx_get_pos_lpib(chip, azx_dev);
+ chip->get_delay[stream] = NULL;
+ } else {
+ chip->get_position[stream] = azx_get_pos_posbuf;
+ if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)
+ chip->get_delay[stream] = azx_get_delay_from_lpib;
+ }
+ }
+
+ if (pos >= azx_dev->core.bufsize)
+ pos = 0;
+
+ if (WARN_ONCE(!azx_dev->core.period_bytes,
+ "hda-intel: zero azx_dev->period_bytes"))
+ return -1; /* this shouldn't happen! */
+ if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 &&
+ pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2)
+ /* NG - it's below the first next period boundary */
+ return chip->bdl_pos_adj ? 0 : -1;
+ azx_dev->core.start_wallclk += wallclk;
+
+ if (azx_dev->core.no_period_wakeup)
+ return 1; /* OK, no need to check period boundary */
+
+ if (runtime->hw_ptr_base != runtime->hw_ptr_interrupt)
+ return 1; /* OK, already in hwptr updating process */
+
+ /* check whether the period gets really elapsed */
+ pos = bytes_to_frames(runtime, pos);
+ hwptr = runtime->hw_ptr_base + pos;
+ if (hwptr < runtime->status->hw_ptr)
+ hwptr += runtime->buffer_size;
+ target = runtime->hw_ptr_interrupt + runtime->period_size;
+ if (hwptr < target) {
+ /* too early wakeup, process it later */
+ return chip->bdl_pos_adj ? 0 : -1;
+ }
+
+ return 1; /* OK, it's fine */
+}
+
+/*
+ * The work for pending PCM period updates.
+ */
+static void azx_irq_pending_work(struct work_struct *work)
+{
+ struct hda_intel *hda = container_of(work, struct hda_intel, irq_pending_work);
+ struct azx *chip = &hda->chip;
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+ int pending, ok;
+
+ if (!hda->irq_pending_warned) {
+ dev_info(chip->card->dev,
+ "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n",
+ chip->card->number);
+ hda->irq_pending_warned = 1;
+ }
+
+ for (;;) {
+ pending = 0;
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(s, &bus->stream_list, list) {
+ struct azx_dev *azx_dev = stream_to_azx_dev(s);
+ if (!azx_dev->irq_pending ||
+ !s->substream ||
+ !s->running)
+ continue;
+ ok = azx_position_ok(chip, azx_dev);
+ if (ok > 0) {
+ azx_dev->irq_pending = 0;
+ spin_unlock(&bus->reg_lock);
+ snd_pcm_period_elapsed(s->substream);
+ spin_lock(&bus->reg_lock);
+ } else if (ok < 0) {
+ pending = 0; /* too early */
+ } else
+ pending++;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+ if (!pending)
+ return;
+ msleep(1);
+ }
+}
+
+/* clear irq_pending flags and assure no on-going workq */
+static void azx_clear_irq_pending(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(s, &bus->stream_list, list) {
+ struct azx_dev *azx_dev = stream_to_azx_dev(s);
+ azx_dev->irq_pending = 0;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+}
+
+static int azx_acquire_irq(struct azx *chip, int do_disconnect)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+
+ if (request_irq(chip->pci->irq, azx_interrupt,
+ chip->msi ? 0 : IRQF_SHARED,
+ chip->card->irq_descr, chip)) {
+ dev_err(chip->card->dev,
+ "unable to grab IRQ %d, disabling device\n",
+ chip->pci->irq);
+ if (do_disconnect)
+ snd_card_disconnect(chip->card);
+ return -1;
+ }
+ bus->irq = chip->pci->irq;
+ chip->card->sync_irq = bus->irq;
+ pci_intx(chip->pci, !chip->msi);
+ return 0;
+}
+
+/* get the current DMA position with correction on VIA chips */
+static unsigned int azx_via_get_position(struct azx *chip,
+ struct azx_dev *azx_dev)
+{
+ unsigned int link_pos, mini_pos, bound_pos;
+ unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos;
+ unsigned int fifo_size;
+
+ link_pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev));
+ if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* Playback, no problem using link position */
+ return link_pos;
+ }
+
+ /* Capture */
+ /* For new chipset,
+ * use mod to get the DMA position just like old chipset
+ */
+ mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf);
+ mod_dma_pos %= azx_dev->core.period_bytes;
+
+ fifo_size = azx_stream(azx_dev)->fifo_size - 1;
+
+ if (azx_dev->insufficient) {
+ /* Link position never gather than FIFO size */
+ if (link_pos <= fifo_size)
+ return 0;
+
+ azx_dev->insufficient = 0;
+ }
+
+ if (link_pos <= fifo_size)
+ mini_pos = azx_dev->core.bufsize + link_pos - fifo_size;
+ else
+ mini_pos = link_pos - fifo_size;
+
+ /* Find nearest previous boudary */
+ mod_mini_pos = mini_pos % azx_dev->core.period_bytes;
+ mod_link_pos = link_pos % azx_dev->core.period_bytes;
+ if (mod_link_pos >= fifo_size)
+ bound_pos = link_pos - mod_link_pos;
+ else if (mod_dma_pos >= mod_mini_pos)
+ bound_pos = mini_pos - mod_mini_pos;
+ else {
+ bound_pos = mini_pos - mod_mini_pos + azx_dev->core.period_bytes;
+ if (bound_pos >= azx_dev->core.bufsize)
+ bound_pos = 0;
+ }
+
+ /* Calculate real DMA position we want */
+ return bound_pos + mod_dma_pos;
+}
+
+#define AMD_FIFO_SIZE 32
+
+/* get the current DMA position with FIFO size correction */
+static unsigned int azx_get_pos_fifo(struct azx *chip, struct azx_dev *azx_dev)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int pos, delay;
+
+ pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev));
+ if (!runtime)
+ return pos;
+
+ runtime->delay = AMD_FIFO_SIZE;
+ delay = frames_to_bytes(runtime, AMD_FIFO_SIZE);
+ if (azx_dev->insufficient) {
+ if (pos < delay) {
+ delay = pos;
+ runtime->delay = bytes_to_frames(runtime, pos);
+ } else {
+ azx_dev->insufficient = 0;
+ }
+ }
+
+ /* correct the DMA position for capture stream */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (pos < delay)
+ pos += azx_dev->core.bufsize;
+ pos -= delay;
+ }
+
+ return pos;
+}
+
+static int azx_get_delay_from_fifo(struct azx *chip, struct azx_dev *azx_dev,
+ unsigned int pos)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+
+ /* just read back the calculated value in the above */
+ return substream->runtime->delay;
+}
+
+static void __azx_shutdown_chip(struct azx *chip, bool skip_link_reset)
+{
+ azx_stop_chip(chip);
+ if (!skip_link_reset)
+ azx_enter_link_reset(chip);
+ azx_clear_irq_pending(chip);
+ display_power(chip, false);
+}
+
+#ifdef CONFIG_PM
+static DEFINE_MUTEX(card_list_lock);
+static LIST_HEAD(card_list);
+
+static void azx_shutdown_chip(struct azx *chip)
+{
+ __azx_shutdown_chip(chip, false);
+}
+
+static void azx_add_card_list(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ mutex_lock(&card_list_lock);
+ list_add(&hda->list, &card_list);
+ mutex_unlock(&card_list_lock);
+}
+
+static void azx_del_card_list(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ mutex_lock(&card_list_lock);
+ list_del_init(&hda->list);
+ mutex_unlock(&card_list_lock);
+}
+
+/* trigger power-save check at writing parameter */
+static int param_set_xint(const char *val, const struct kernel_param *kp)
+{
+ struct hda_intel *hda;
+ struct azx *chip;
+ int prev = power_save;
+ int ret = param_set_int(val, kp);
+
+ if (ret || prev == power_save)
+ return ret;
+
+ mutex_lock(&card_list_lock);
+ list_for_each_entry(hda, &card_list, list) {
+ chip = &hda->chip;
+ if (!hda->probe_continued || chip->disabled)
+ continue;
+ snd_hda_set_power_save(&chip->bus, power_save * 1000);
+ }
+ mutex_unlock(&card_list_lock);
+ return 0;
+}
+
+/*
+ * power management
+ */
+static bool azx_is_pm_ready(struct snd_card *card)
+{
+ struct azx *chip;
+ struct hda_intel *hda;
+
+ if (!card)
+ return false;
+ chip = card->private_data;
+ hda = container_of(chip, struct hda_intel, chip);
+ if (chip->disabled || hda->init_failed || !chip->running)
+ return false;
+ return true;
+}
+
+static void __azx_runtime_resume(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hda_codec *codec;
+ int status;
+
+ display_power(chip, true);
+ if (hda->need_i915_power)
+ snd_hdac_i915_set_bclk(bus);
+
+ /* Read STATESTS before controller reset */
+ status = azx_readw(chip, STATESTS);
+
+ azx_init_pci(chip);
+ hda_intel_init_chip(chip, true);
+
+ /* Avoid codec resume if runtime resume is for system suspend */
+ if (!chip->pm_prepared) {
+ list_for_each_codec(codec, &chip->bus) {
+ if (codec->relaxed_resume)
+ continue;
+
+ if (codec->forced_resume || (status & (1 << codec->addr)))
+ pm_request_resume(hda_codec_dev(codec));
+ }
+ }
+
+ /* power down again for link-controlled chips */
+ if (!hda->need_i915_power)
+ display_power(chip, false);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int azx_prepare(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+
+ chip = card->private_data;
+ chip->pm_prepared = 1;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+ flush_work(&azx_bus(chip)->unsol_work);
+
+ /* HDA controller always requires different WAKEEN for runtime suspend
+ * and system suspend, so don't use direct-complete here.
+ */
+ return 0;
+}
+
+static void azx_complete(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+
+ if (!azx_is_pm_ready(card))
+ return;
+
+ chip = card->private_data;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ chip->pm_prepared = 0;
+}
+
+static int azx_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+ struct hdac_bus *bus;
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+
+ chip = card->private_data;
+ bus = azx_bus(chip);
+ azx_shutdown_chip(chip);
+ if (bus->irq >= 0) {
+ free_irq(bus->irq, chip);
+ bus->irq = -1;
+ chip->card->sync_irq = -1;
+ }
+
+ if (chip->msi)
+ pci_disable_msi(chip->pci);
+
+ trace_azx_suspend(chip);
+ return 0;
+}
+
+static int azx_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+
+ chip = card->private_data;
+ if (chip->msi)
+ if (pci_enable_msi(chip->pci) < 0)
+ chip->msi = 0;
+ if (azx_acquire_irq(chip, 1) < 0)
+ return -EIO;
+
+ __azx_runtime_resume(chip);
+
+ trace_azx_resume(chip);
+ return 0;
+}
+
+/* put codec down to D3 at hibernation for Intel SKL+;
+ * otherwise BIOS may still access the codec and screw up the driver
+ */
+static int azx_freeze_noirq(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+ struct pci_dev *pci = to_pci_dev(dev);
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+ if (chip->driver_type == AZX_DRIVER_SKL)
+ pci_set_power_state(pci, PCI_D3hot);
+
+ return 0;
+}
+
+static int azx_thaw_noirq(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+ struct pci_dev *pci = to_pci_dev(dev);
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+ if (chip->driver_type == AZX_DRIVER_SKL)
+ pci_set_power_state(pci, PCI_D0);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static int azx_runtime_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+ chip = card->private_data;
+
+ /* enable controller wake up event */
+ azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | STATESTS_INT_MASK);
+
+ azx_shutdown_chip(chip);
+ trace_azx_runtime_suspend(chip);
+ return 0;
+}
+
+static int azx_runtime_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+
+ if (!azx_is_pm_ready(card))
+ return 0;
+ chip = card->private_data;
+ __azx_runtime_resume(chip);
+
+ /* disable controller Wake Up event*/
+ azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & ~STATESTS_INT_MASK);
+
+ trace_azx_runtime_resume(chip);
+ return 0;
+}
+
+static int azx_runtime_idle(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip;
+ struct hda_intel *hda;
+
+ if (!card)
+ return 0;
+
+ chip = card->private_data;
+ hda = container_of(chip, struct hda_intel, chip);
+ if (chip->disabled || hda->init_failed)
+ return 0;
+
+ if (!power_save_controller || !azx_has_pm_runtime(chip) ||
+ azx_bus(chip)->codec_powered || !chip->running)
+ return -EBUSY;
+
+ /* ELD notification gets broken when HD-audio bus is off */
+ if (needs_eld_notify_link(chip))
+ return -EBUSY;
+
+ return 0;
+}
+
+static const struct dev_pm_ops azx_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume)
+#ifdef CONFIG_PM_SLEEP
+ .prepare = azx_prepare,
+ .complete = azx_complete,
+ .freeze_noirq = azx_freeze_noirq,
+ .thaw_noirq = azx_thaw_noirq,
+#endif
+ SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, azx_runtime_idle)
+};
+
+#define AZX_PM_OPS &azx_pm
+#else
+#define azx_add_card_list(chip) /* NOP */
+#define azx_del_card_list(chip) /* NOP */
+#define AZX_PM_OPS NULL
+#endif /* CONFIG_PM */
+
+
+static int azx_probe_continue(struct azx *chip);
+
+#ifdef SUPPORT_VGA_SWITCHEROO
+static struct pci_dev *get_bound_vga(struct pci_dev *pci);
+
+static void azx_vs_set_state(struct pci_dev *pci,
+ enum vga_switcheroo_state state)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct azx *chip = card->private_data;
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hda_codec *codec;
+ bool disabled;
+
+ wait_for_completion(&hda->probe_wait);
+ if (hda->init_failed)
+ return;
+
+ disabled = (state == VGA_SWITCHEROO_OFF);
+ if (chip->disabled == disabled)
+ return;
+
+ if (!hda->probe_continued) {
+ chip->disabled = disabled;
+ if (!disabled) {
+ dev_info(chip->card->dev,
+ "Start delayed initialization\n");
+ if (azx_probe_continue(chip) < 0)
+ dev_err(chip->card->dev, "initialization error\n");
+ }
+ } else {
+ dev_info(chip->card->dev, "%s via vga_switcheroo\n",
+ disabled ? "Disabling" : "Enabling");
+ if (disabled) {
+ list_for_each_codec(codec, &chip->bus) {
+ pm_runtime_suspend(hda_codec_dev(codec));
+ pm_runtime_disable(hda_codec_dev(codec));
+ }
+ pm_runtime_suspend(card->dev);
+ pm_runtime_disable(card->dev);
+ /* when we get suspended by vga_switcheroo we end up in D3cold,
+ * however we have no ACPI handle, so pci/acpi can't put us there,
+ * put ourselves there */
+ pci->current_state = PCI_D3cold;
+ chip->disabled = true;
+ if (snd_hda_lock_devices(&chip->bus))
+ dev_warn(chip->card->dev,
+ "Cannot lock devices!\n");
+ } else {
+ snd_hda_unlock_devices(&chip->bus);
+ chip->disabled = false;
+ pm_runtime_enable(card->dev);
+ list_for_each_codec(codec, &chip->bus) {
+ pm_runtime_enable(hda_codec_dev(codec));
+ pm_runtime_resume(hda_codec_dev(codec));
+ }
+ }
+ }
+}
+
+static bool azx_vs_can_switch(struct pci_dev *pci)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct azx *chip = card->private_data;
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+
+ wait_for_completion(&hda->probe_wait);
+ if (hda->init_failed)
+ return false;
+ if (chip->disabled || !hda->probe_continued)
+ return true;
+ if (snd_hda_lock_devices(&chip->bus))
+ return false;
+ snd_hda_unlock_devices(&chip->bus);
+ return true;
+}
+
+/*
+ * The discrete GPU cannot power down unless the HDA controller runtime
+ * suspends, so activate runtime PM on codecs even if power_save == 0.
+ */
+static void setup_vga_switcheroo_runtime_pm(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hda_codec *codec;
+
+ if (hda->use_vga_switcheroo && !needs_eld_notify_link(chip)) {
+ list_for_each_codec(codec, &chip->bus)
+ codec->auto_runtime_pm = 1;
+ /* reset the power save setup */
+ if (chip->running)
+ set_default_power_save(chip);
+ }
+}
+
+static void azx_vs_gpu_bound(struct pci_dev *pci,
+ enum vga_switcheroo_client_id client_id)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct azx *chip = card->private_data;
+
+ if (client_id == VGA_SWITCHEROO_DIS)
+ chip->bus.keep_power = 0;
+ setup_vga_switcheroo_runtime_pm(chip);
+}
+
+static void init_vga_switcheroo(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct pci_dev *p = get_bound_vga(chip->pci);
+ struct pci_dev *parent;
+ if (p) {
+ dev_info(chip->card->dev,
+ "Handle vga_switcheroo audio client\n");
+ hda->use_vga_switcheroo = 1;
+
+ /* cleared in either gpu_bound op or codec probe, or when its
+ * upstream port has _PR3 (i.e. dGPU).
+ */
+ parent = pci_upstream_bridge(p);
+ chip->bus.keep_power = parent ? !pci_pr3_present(parent) : 1;
+ chip->driver_caps |= AZX_DCAPS_PM_RUNTIME;
+ pci_dev_put(p);
+ }
+}
+
+static const struct vga_switcheroo_client_ops azx_vs_ops = {
+ .set_gpu_state = azx_vs_set_state,
+ .can_switch = azx_vs_can_switch,
+ .gpu_bound = azx_vs_gpu_bound,
+};
+
+static int register_vga_switcheroo(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct pci_dev *p;
+ int err;
+
+ if (!hda->use_vga_switcheroo)
+ return 0;
+
+ p = get_bound_vga(chip->pci);
+ err = vga_switcheroo_register_audio_client(chip->pci, &azx_vs_ops, p);
+ pci_dev_put(p);
+
+ if (err < 0)
+ return err;
+ hda->vga_switcheroo_registered = 1;
+
+ return 0;
+}
+#else
+#define init_vga_switcheroo(chip) /* NOP */
+#define register_vga_switcheroo(chip) 0
+#define check_hdmi_disabled(pci) false
+#define setup_vga_switcheroo_runtime_pm(chip) /* NOP */
+#endif /* SUPPORT_VGA_SWITCHER */
+
+/*
+ * destructor
+ */
+static void azx_free(struct azx *chip)
+{
+ struct pci_dev *pci = chip->pci;
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hdac_bus *bus = azx_bus(chip);
+
+ if (hda->freed)
+ return;
+
+ if (azx_has_pm_runtime(chip) && chip->running)
+ pm_runtime_get_noresume(&pci->dev);
+ chip->running = 0;
+
+ azx_del_card_list(chip);
+
+ hda->init_failed = 1; /* to be sure */
+ complete_all(&hda->probe_wait);
+
+ if (use_vga_switcheroo(hda)) {
+ if (chip->disabled && hda->probe_continued)
+ snd_hda_unlock_devices(&chip->bus);
+ if (hda->vga_switcheroo_registered)
+ vga_switcheroo_unregister_client(chip->pci);
+ }
+
+ if (bus->chip_init) {
+ azx_clear_irq_pending(chip);
+ azx_stop_all_streams(chip);
+ azx_stop_chip(chip);
+ }
+
+ if (bus->irq >= 0)
+ free_irq(bus->irq, (void*)chip);
+ if (chip->msi)
+ pci_disable_msi(chip->pci);
+ iounmap(bus->remap_addr);
+
+ azx_free_stream_pages(chip);
+ azx_free_streams(chip);
+ snd_hdac_bus_exit(bus);
+
+ if (chip->region_requested)
+ pci_release_regions(chip->pci);
+
+ pci_disable_device(chip->pci);
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ release_firmware(chip->fw);
+#endif
+ display_power(chip, false);
+
+ if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT)
+ snd_hdac_i915_exit(bus);
+
+ hda->freed = 1;
+}
+
+static int azx_dev_disconnect(struct snd_device *device)
+{
+ struct azx *chip = device->device_data;
+ struct hdac_bus *bus = azx_bus(chip);
+
+ chip->bus.shutdown = 1;
+ cancel_work_sync(&bus->unsol_work);
+
+ return 0;
+}
+
+static int azx_dev_free(struct snd_device *device)
+{
+ azx_free(device->device_data);
+ return 0;
+}
+
+#ifdef SUPPORT_VGA_SWITCHEROO
+#ifdef CONFIG_ACPI
+/* ATPX is in the integrated GPU's namespace */
+static bool atpx_present(void)
+{
+ struct pci_dev *pdev = NULL;
+ acpi_handle dhandle, atpx_handle;
+ acpi_status status;
+
+ while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) {
+ dhandle = ACPI_HANDLE(&pdev->dev);
+ if (dhandle) {
+ status = acpi_get_handle(dhandle, "ATPX", &atpx_handle);
+ if (!ACPI_FAILURE(status)) {
+ pci_dev_put(pdev);
+ return true;
+ }
+ }
+ }
+ while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_OTHER << 8, pdev)) != NULL) {
+ dhandle = ACPI_HANDLE(&pdev->dev);
+ if (dhandle) {
+ status = acpi_get_handle(dhandle, "ATPX", &atpx_handle);
+ if (!ACPI_FAILURE(status)) {
+ pci_dev_put(pdev);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#else
+static bool atpx_present(void)
+{
+ return false;
+}
+#endif
+
+/*
+ * Check of disabled HDMI controller by vga_switcheroo
+ */
+static struct pci_dev *get_bound_vga(struct pci_dev *pci)
+{
+ struct pci_dev *p;
+
+ /* check only discrete GPU */
+ switch (pci->vendor) {
+ case PCI_VENDOR_ID_ATI:
+ case PCI_VENDOR_ID_AMD:
+ if (pci->devfn == 1) {
+ p = pci_get_domain_bus_and_slot(pci_domain_nr(pci->bus),
+ pci->bus->number, 0);
+ if (p) {
+ /* ATPX is in the integrated GPU's ACPI namespace
+ * rather than the dGPU's namespace. However,
+ * the dGPU is the one who is involved in
+ * vgaswitcheroo.
+ */
+ if (((p->class >> 16) == PCI_BASE_CLASS_DISPLAY) &&
+ atpx_present())
+ return p;
+ pci_dev_put(p);
+ }
+ }
+ break;
+ case PCI_VENDOR_ID_NVIDIA:
+ if (pci->devfn == 1) {
+ p = pci_get_domain_bus_and_slot(pci_domain_nr(pci->bus),
+ pci->bus->number, 0);
+ if (p) {
+ if ((p->class >> 16) == PCI_BASE_CLASS_DISPLAY)
+ return p;
+ pci_dev_put(p);
+ }
+ }
+ break;
+ }
+ return NULL;
+}
+
+static bool check_hdmi_disabled(struct pci_dev *pci)
+{
+ bool vga_inactive = false;
+ struct pci_dev *p = get_bound_vga(pci);
+
+ if (p) {
+ if (vga_switcheroo_get_client_state(p) == VGA_SWITCHEROO_OFF)
+ vga_inactive = true;
+ pci_dev_put(p);
+ }
+ return vga_inactive;
+}
+#endif /* SUPPORT_VGA_SWITCHEROO */
+
+/*
+ * allow/deny-listing for position_fix
+ */
+static const struct snd_pci_quirk position_fix_list[] = {
+ SND_PCI_QUIRK(0x1028, 0x01cc, "Dell D820", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1028, 0x01de, "Dell Precision 390", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x103c, 0x306d, "HP dv3", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1043, 0x81b3, "ASUS", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS M2V", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x104d, 0x9069, "Sony VPCS11V9E", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x10de, 0xcb89, "Macbook Pro 7,1", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1297, 0x3166, "Shuttle", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1458, 0xa022, "ga-ma770-ud3", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1462, 0x1002, "MSI Wind U115", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1565, 0x8218, "Biostar Microtech", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1849, 0x0888, "775Dual-VSTA", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x8086, 0x2503, "DG965OT AAD63733-203", POS_FIX_LPIB),
+ {}
+};
+
+static int check_position_fix(struct azx *chip, int fix)
+{
+ const struct snd_pci_quirk *q;
+
+ switch (fix) {
+ case POS_FIX_AUTO:
+ case POS_FIX_LPIB:
+ case POS_FIX_POSBUF:
+ case POS_FIX_VIACOMBO:
+ case POS_FIX_COMBO:
+ case POS_FIX_SKL:
+ case POS_FIX_FIFO:
+ return fix;
+ }
+
+ q = snd_pci_quirk_lookup(chip->pci, position_fix_list);
+ if (q) {
+ dev_info(chip->card->dev,
+ "position_fix set to %d for device %04x:%04x\n",
+ q->value, q->subvendor, q->subdevice);
+ return q->value;
+ }
+
+ /* Check VIA/ATI HD Audio Controller exist */
+ if (chip->driver_type == AZX_DRIVER_VIA) {
+ dev_dbg(chip->card->dev, "Using VIACOMBO position fix\n");
+ return POS_FIX_VIACOMBO;
+ }
+ if (chip->driver_caps & AZX_DCAPS_AMD_WORKAROUND) {
+ dev_dbg(chip->card->dev, "Using FIFO position fix\n");
+ return POS_FIX_FIFO;
+ }
+ if (chip->driver_caps & AZX_DCAPS_POSFIX_LPIB) {
+ dev_dbg(chip->card->dev, "Using LPIB position fix\n");
+ return POS_FIX_LPIB;
+ }
+ if (chip->driver_type == AZX_DRIVER_SKL) {
+ dev_dbg(chip->card->dev, "Using SKL position fix\n");
+ return POS_FIX_SKL;
+ }
+ return POS_FIX_AUTO;
+}
+
+static void assign_position_fix(struct azx *chip, int fix)
+{
+ static const azx_get_pos_callback_t callbacks[] = {
+ [POS_FIX_AUTO] = NULL,
+ [POS_FIX_LPIB] = azx_get_pos_lpib,
+ [POS_FIX_POSBUF] = azx_get_pos_posbuf,
+ [POS_FIX_VIACOMBO] = azx_via_get_position,
+ [POS_FIX_COMBO] = azx_get_pos_lpib,
+ [POS_FIX_SKL] = azx_get_pos_posbuf,
+ [POS_FIX_FIFO] = azx_get_pos_fifo,
+ };
+
+ chip->get_position[0] = chip->get_position[1] = callbacks[fix];
+
+ /* combo mode uses LPIB only for playback */
+ if (fix == POS_FIX_COMBO)
+ chip->get_position[1] = NULL;
+
+ if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) &&
+ (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) {
+ chip->get_delay[0] = chip->get_delay[1] =
+ azx_get_delay_from_lpib;
+ }
+
+ if (fix == POS_FIX_FIFO)
+ chip->get_delay[0] = chip->get_delay[1] =
+ azx_get_delay_from_fifo;
+}
+
+/*
+ * deny-lists for probe_mask
+ */
+static const struct snd_pci_quirk probe_mask_list[] = {
+ /* Thinkpad often breaks the controller communication when accessing
+ * to the non-working (or non-existing) modem codec slot.
+ */
+ SND_PCI_QUIRK(0x1014, 0x05b7, "Thinkpad Z60", 0x01),
+ SND_PCI_QUIRK(0x17aa, 0x2010, "Thinkpad X/T/R60", 0x01),
+ SND_PCI_QUIRK(0x17aa, 0x20ac, "Thinkpad X/T/R61", 0x01),
+ /* broken BIOS */
+ SND_PCI_QUIRK(0x1028, 0x20ac, "Dell Studio Desktop", 0x01),
+ /* including bogus ALC268 in slot#2 that conflicts with ALC888 */
+ SND_PCI_QUIRK(0x17c0, 0x4085, "Medion MD96630", 0x01),
+ /* forced codec slots */
+ SND_PCI_QUIRK(0x1043, 0x1262, "ASUS W5Fm", 0x103),
+ SND_PCI_QUIRK(0x1046, 0x1262, "ASUS W5F", 0x103),
+ SND_PCI_QUIRK(0x1558, 0x0351, "Schenker Dock 15", 0x105),
+ /* WinFast VP200 H (Teradici) user reported broken communication */
+ SND_PCI_QUIRK(0x3a21, 0x040d, "WinFast VP200 H", 0x101),
+ {}
+};
+
+#define AZX_FORCE_CODEC_MASK 0x100
+
+static void check_probe_mask(struct azx *chip, int dev)
+{
+ const struct snd_pci_quirk *q;
+
+ chip->codec_probe_mask = probe_mask[dev];
+ if (chip->codec_probe_mask == -1) {
+ q = snd_pci_quirk_lookup(chip->pci, probe_mask_list);
+ if (q) {
+ dev_info(chip->card->dev,
+ "probe_mask set to 0x%x for device %04x:%04x\n",
+ q->value, q->subvendor, q->subdevice);
+ chip->codec_probe_mask = q->value;
+ }
+ }
+
+ /* check forced option */
+ if (chip->codec_probe_mask != -1 &&
+ (chip->codec_probe_mask & AZX_FORCE_CODEC_MASK)) {
+ azx_bus(chip)->codec_mask = chip->codec_probe_mask & 0xff;
+ dev_info(chip->card->dev, "codec_mask forced to 0x%x\n",
+ (int)azx_bus(chip)->codec_mask);
+ }
+}
+
+/*
+ * allow/deny-list for enable_msi
+ */
+static const struct snd_pci_quirk msi_deny_list[] = {
+ SND_PCI_QUIRK(0x103c, 0x2191, "HP", 0), /* AMD Hudson */
+ SND_PCI_QUIRK(0x103c, 0x2192, "HP", 0), /* AMD Hudson */
+ SND_PCI_QUIRK(0x103c, 0x21f7, "HP", 0), /* AMD Hudson */
+ SND_PCI_QUIRK(0x103c, 0x21fa, "HP", 0), /* AMD Hudson */
+ SND_PCI_QUIRK(0x1043, 0x81f2, "ASUS", 0), /* Athlon64 X2 + nvidia */
+ SND_PCI_QUIRK(0x1043, 0x81f6, "ASUS", 0), /* nvidia */
+ SND_PCI_QUIRK(0x1043, 0x822d, "ASUS", 0), /* Athlon64 X2 + nvidia MCP55 */
+ SND_PCI_QUIRK(0x1179, 0xfb44, "Toshiba Satellite C870", 0), /* AMD Hudson */
+ SND_PCI_QUIRK(0x1849, 0x0888, "ASRock", 0), /* Athlon64 X2 + nvidia */
+ SND_PCI_QUIRK(0xa0a0, 0x0575, "Aopen MZ915-M", 0), /* ICH6 */
+ {}
+};
+
+static void check_msi(struct azx *chip)
+{
+ const struct snd_pci_quirk *q;
+
+ if (enable_msi >= 0) {
+ chip->msi = !!enable_msi;
+ return;
+ }
+ chip->msi = 1; /* enable MSI as default */
+ q = snd_pci_quirk_lookup(chip->pci, msi_deny_list);
+ if (q) {
+ dev_info(chip->card->dev,
+ "msi for device %04x:%04x set to %d\n",
+ q->subvendor, q->subdevice, q->value);
+ chip->msi = q->value;
+ return;
+ }
+
+ /* NVidia chipsets seem to cause troubles with MSI */
+ if (chip->driver_caps & AZX_DCAPS_NO_MSI) {
+ dev_info(chip->card->dev, "Disabling MSI\n");
+ chip->msi = 0;
+ }
+}
+
+/* check the snoop mode availability */
+static void azx_check_snoop_available(struct azx *chip)
+{
+ int snoop = hda_snoop;
+
+ if (snoop >= 0) {
+ dev_info(chip->card->dev, "Force to %s mode by module option\n",
+ snoop ? "snoop" : "non-snoop");
+ chip->snoop = snoop;
+ chip->uc_buffer = !snoop;
+ return;
+ }
+
+ snoop = true;
+ if (azx_get_snoop_type(chip) == AZX_SNOOP_TYPE_NONE &&
+ chip->driver_type == AZX_DRIVER_VIA) {
+ /* force to non-snoop mode for a new VIA controller
+ * when BIOS is set
+ */
+ u8 val;
+ pci_read_config_byte(chip->pci, 0x42, &val);
+ if (!(val & 0x80) && (chip->pci->revision == 0x30 ||
+ chip->pci->revision == 0x20))
+ snoop = false;
+ }
+
+ if (chip->driver_caps & AZX_DCAPS_SNOOP_OFF)
+ snoop = false;
+
+ chip->snoop = snoop;
+ if (!snoop) {
+ dev_info(chip->card->dev, "Force to non-snoop mode\n");
+ /* C-Media requires non-cached pages only for CORB/RIRB */
+ if (chip->driver_type != AZX_DRIVER_CMEDIA)
+ chip->uc_buffer = true;
+ }
+}
+
+static void azx_probe_work(struct work_struct *work)
+{
+ struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work);
+ azx_probe_continue(&hda->chip);
+}
+
+static int default_bdl_pos_adj(struct azx *chip)
+{
+ /* some exceptions: Atoms seem problematic with value 1 */
+ if (chip->pci->vendor == PCI_VENDOR_ID_INTEL) {
+ switch (chip->pci->device) {
+ case 0x0f04: /* Baytrail */
+ case 0x2284: /* Braswell */
+ return 32;
+ }
+ }
+
+ switch (chip->driver_type) {
+ /*
+ * increase the bdl size for Glenfly Gpus for hardware
+ * limitation on hdac interrupt interval
+ */
+ case AZX_DRIVER_GFHDMI:
+ return 128;
+ case AZX_DRIVER_ICH:
+ case AZX_DRIVER_PCH:
+ return 1;
+ default:
+ return 32;
+ }
+}
+
+/*
+ * constructor
+ */
+static const struct hda_controller_ops pci_hda_ops;
+
+static int azx_create(struct snd_card *card, struct pci_dev *pci,
+ int dev, unsigned int driver_caps,
+ struct azx **rchip)
+{
+ static const struct snd_device_ops ops = {
+ .dev_disconnect = azx_dev_disconnect,
+ .dev_free = azx_dev_free,
+ };
+ struct hda_intel *hda;
+ struct azx *chip;
+ int err;
+
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL);
+ if (!hda) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip = &hda->chip;
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->pci = pci;
+ chip->ops = &pci_hda_ops;
+ chip->driver_caps = driver_caps;
+ chip->driver_type = driver_caps & 0xff;
+ check_msi(chip);
+ chip->dev_index = dev;
+ if (jackpoll_ms[dev] >= 50 && jackpoll_ms[dev] <= 60000)
+ chip->jackpoll_interval = msecs_to_jiffies(jackpoll_ms[dev]);
+ INIT_LIST_HEAD(&chip->pcm_list);
+ INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work);
+ INIT_LIST_HEAD(&hda->list);
+ init_vga_switcheroo(chip);
+ init_completion(&hda->probe_wait);
+
+ assign_position_fix(chip, check_position_fix(chip, position_fix[dev]));
+
+ if (single_cmd < 0) /* allow fallback to single_cmd at errors */
+ chip->fallback_to_single_cmd = 1;
+ else /* explicitly set to single_cmd or not */
+ chip->single_cmd = single_cmd;
+
+ azx_check_snoop_available(chip);
+
+ if (bdl_pos_adj[dev] < 0)
+ chip->bdl_pos_adj = default_bdl_pos_adj(chip);
+ else
+ chip->bdl_pos_adj = bdl_pos_adj[dev];
+
+ err = azx_bus_init(chip, model[dev]);
+ if (err < 0) {
+ pci_disable_device(pci);
+ return err;
+ }
+
+ /* use the non-cached pages in non-snoop mode */
+ if (!azx_snoop(chip))
+ azx_bus(chip)->dma_type = SNDRV_DMA_TYPE_DEV_UC;
+
+ if (chip->driver_type == AZX_DRIVER_NVIDIA) {
+ dev_dbg(chip->card->dev, "Enable delay in RIRB handling\n");
+ chip->bus.core.needs_damn_long_delay = 1;
+ }
+
+ check_probe_mask(chip, dev);
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ dev_err(card->dev, "Error creating device [card]!\n");
+ azx_free(chip);
+ return err;
+ }
+
+ /* continue probing in work context as may trigger request module */
+ INIT_DELAYED_WORK(&hda->probe_work, azx_probe_work);
+
+ *rchip = chip;
+
+ return 0;
+}
+
+static int azx_first_init(struct azx *chip)
+{
+ int dev = chip->dev_index;
+ struct pci_dev *pci = chip->pci;
+ struct snd_card *card = chip->card;
+ struct hdac_bus *bus = azx_bus(chip);
+ int err;
+ unsigned short gcap;
+ unsigned int dma_bits = 64;
+
+#if BITS_PER_LONG != 64
+ /* Fix up base address on ULI M5461 */
+ if (chip->driver_type == AZX_DRIVER_ULI) {
+ u16 tmp3;
+ pci_read_config_word(pci, 0x40, &tmp3);
+ pci_write_config_word(pci, 0x40, tmp3 | 0x10);
+ pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, 0);
+ }
+#endif
+ /*
+ * Fix response write request not synced to memory when handle
+ * hdac interrupt on Glenfly Gpus
+ */
+ if (chip->driver_type == AZX_DRIVER_GFHDMI)
+ bus->polling_mode = 1;
+
+ err = pci_request_regions(pci, "ICH HD audio");
+ if (err < 0)
+ return err;
+ chip->region_requested = 1;
+
+ bus->addr = pci_resource_start(pci, 0);
+ bus->remap_addr = pci_ioremap_bar(pci, 0);
+ if (bus->remap_addr == NULL) {
+ dev_err(card->dev, "ioremap error\n");
+ return -ENXIO;
+ }
+
+ if (chip->driver_type == AZX_DRIVER_SKL)
+ snd_hdac_bus_parse_capabilities(bus);
+
+ /*
+ * Some Intel CPUs has always running timer (ART) feature and
+ * controller may have Global time sync reporting capability, so
+ * check both of these before declaring synchronized time reporting
+ * capability SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME
+ */
+ chip->gts_present = false;
+
+#ifdef CONFIG_X86
+ if (bus->ppcap && boot_cpu_has(X86_FEATURE_ART))
+ chip->gts_present = true;
+#endif
+
+ if (chip->msi) {
+ if (chip->driver_caps & AZX_DCAPS_NO_MSI64) {
+ dev_dbg(card->dev, "Disabling 64bit MSI\n");
+ pci->no_64bit_msi = true;
+ }
+ if (pci_enable_msi(pci) < 0)
+ chip->msi = 0;
+ }
+
+ pci_set_master(pci);
+
+ gcap = azx_readw(chip, GCAP);
+ dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
+
+ /* AMD devices support 40 or 48bit DMA, take the safe one */
+ if (chip->pci->vendor == PCI_VENDOR_ID_AMD)
+ dma_bits = 40;
+
+ /* disable SB600 64bit support for safety */
+ if (chip->pci->vendor == PCI_VENDOR_ID_ATI) {
+ struct pci_dev *p_smbus;
+ dma_bits = 40;
+ p_smbus = pci_get_device(PCI_VENDOR_ID_ATI,
+ PCI_DEVICE_ID_ATI_SBX00_SMBUS,
+ NULL);
+ if (p_smbus) {
+ if (p_smbus->revision < 0x30)
+ gcap &= ~AZX_GCAP_64OK;
+ pci_dev_put(p_smbus);
+ }
+ }
+
+ /* NVidia hardware normally only supports up to 40 bits of DMA */
+ if (chip->pci->vendor == PCI_VENDOR_ID_NVIDIA)
+ dma_bits = 40;
+
+ /* disable 64bit DMA address on some devices */
+ if (chip->driver_caps & AZX_DCAPS_NO_64BIT) {
+ dev_dbg(card->dev, "Disabling 64bit DMA\n");
+ gcap &= ~AZX_GCAP_64OK;
+ }
+
+ /* disable buffer size rounding to 128-byte multiples if supported */
+ if (align_buffer_size >= 0)
+ chip->align_buffer_size = !!align_buffer_size;
+ else {
+ if (chip->driver_caps & AZX_DCAPS_NO_ALIGN_BUFSIZE)
+ chip->align_buffer_size = 0;
+ else
+ chip->align_buffer_size = 1;
+ }
+
+ /* allow 64bit DMA address if supported by H/W */
+ if (!(gcap & AZX_GCAP_64OK))
+ dma_bits = 32;
+ if (!dma_set_mask(&pci->dev, DMA_BIT_MASK(dma_bits))) {
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(dma_bits));
+ } else {
+ dma_set_mask(&pci->dev, DMA_BIT_MASK(32));
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32));
+ }
+
+ /* read number of streams from GCAP register instead of using
+ * hardcoded value
+ */
+ chip->capture_streams = (gcap >> 8) & 0x0f;
+ chip->playback_streams = (gcap >> 12) & 0x0f;
+ if (!chip->playback_streams && !chip->capture_streams) {
+ /* gcap didn't give any info, switching to old method */
+
+ switch (chip->driver_type) {
+ case AZX_DRIVER_ULI:
+ chip->playback_streams = ULI_NUM_PLAYBACK;
+ chip->capture_streams = ULI_NUM_CAPTURE;
+ break;
+ case AZX_DRIVER_ATIHDMI:
+ case AZX_DRIVER_ATIHDMI_NS:
+ chip->playback_streams = ATIHDMI_NUM_PLAYBACK;
+ chip->capture_streams = ATIHDMI_NUM_CAPTURE;
+ break;
+ case AZX_DRIVER_GFHDMI:
+ case AZX_DRIVER_GENERIC:
+ default:
+ chip->playback_streams = ICH6_NUM_PLAYBACK;
+ chip->capture_streams = ICH6_NUM_CAPTURE;
+ break;
+ }
+ }
+ chip->capture_index_offset = 0;
+ chip->playback_index_offset = chip->capture_streams;
+ chip->num_streams = chip->playback_streams + chip->capture_streams;
+
+ /* sanity check for the SDxCTL.STRM field overflow */
+ if (chip->num_streams > 15 &&
+ (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG) == 0) {
+ dev_warn(chip->card->dev, "number of I/O streams is %d, "
+ "forcing separate stream tags", chip->num_streams);
+ chip->driver_caps |= AZX_DCAPS_SEPARATE_STREAM_TAG;
+ }
+
+ /* initialize streams */
+ err = azx_init_streams(chip);
+ if (err < 0)
+ return err;
+
+ err = azx_alloc_stream_pages(chip);
+ if (err < 0)
+ return err;
+
+ /* initialize chip */
+ azx_init_pci(chip);
+
+ snd_hdac_i915_set_bclk(bus);
+
+ hda_intel_init_chip(chip, (probe_only[dev] & 2) == 0);
+
+ /* codec detection */
+ if (!azx_bus(chip)->codec_mask) {
+ dev_err(card->dev, "no codecs found!\n");
+ /* keep running the rest for the runtime PM */
+ }
+
+ if (azx_acquire_irq(chip, 0) < 0)
+ return -EBUSY;
+
+ strcpy(card->driver, "HDA-Intel");
+ strlcpy(card->shortname, driver_short_names[chip->driver_type],
+ sizeof(card->shortname));
+ snprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx irq %i",
+ card->shortname, bus->addr, bus->irq);
+
+ return 0;
+}
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+/* callback from request_firmware_nowait() */
+static void azx_firmware_cb(const struct firmware *fw, void *context)
+{
+ struct snd_card *card = context;
+ struct azx *chip = card->private_data;
+
+ if (fw)
+ chip->fw = fw;
+ else
+ dev_err(card->dev, "Cannot load firmware, continue without patching\n");
+ if (!chip->disabled) {
+ /* continue probing */
+ azx_probe_continue(chip);
+ }
+}
+#endif
+
+static int disable_msi_reset_irq(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ int err;
+
+ free_irq(bus->irq, chip);
+ bus->irq = -1;
+ chip->card->sync_irq = -1;
+ pci_disable_msi(chip->pci);
+ chip->msi = 0;
+ err = azx_acquire_irq(chip, 1);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static void pcm_mmap_prepare(struct snd_pcm_substream *substream,
+ struct vm_area_struct *area)
+{
+#ifdef CONFIG_X86
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ if (chip->uc_buffer)
+ area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
+#endif
+}
+
+/* Denylist for skipping the whole probe:
+ * some HD-audio PCI entries are exposed without any codecs, and such devices
+ * should be ignored from the beginning.
+ */
+static const struct pci_device_id driver_denylist[] = {
+ { PCI_DEVICE_SUB(0x1022, 0x1487, 0x1043, 0x874f) }, /* ASUS ROG Zenith II / Strix */
+ { PCI_DEVICE_SUB(0x1022, 0x1487, 0x1462, 0xcb59) }, /* MSI TRX40 Creator */
+ { PCI_DEVICE_SUB(0x1022, 0x1487, 0x1462, 0xcb60) }, /* MSI TRX40 */
+ {}
+};
+
+static const struct hda_controller_ops pci_hda_ops = {
+ .disable_msi_reset_irq = disable_msi_reset_irq,
+ .pcm_mmap_prepare = pcm_mmap_prepare,
+ .position_check = azx_position_check,
+};
+
+static int azx_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ struct snd_card *card;
+ struct hda_intel *hda;
+ struct azx *chip;
+ bool schedule_probe;
+ int err;
+
+ if (pci_match_id(driver_denylist, pci)) {
+ dev_info(&pci->dev, "Skipping the device on the denylist\n");
+ return -ENODEV;
+ }
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ /*
+ * stop probe if another Intel's DSP driver should be activated
+ */
+ if (dmic_detect) {
+ err = snd_intel_dsp_driver_probe(pci);
+ if (err != SND_INTEL_DSP_DRIVER_ANY && err != SND_INTEL_DSP_DRIVER_LEGACY) {
+ dev_dbg(&pci->dev, "HDAudio driver not selected, aborting probe\n");
+ return -ENODEV;
+ }
+ } else {
+ dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n");
+ }
+
+ err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0) {
+ dev_err(&pci->dev, "Error creating card!\n");
+ return err;
+ }
+
+ err = azx_create(card, pci, dev, pci_id->driver_data, &chip);
+ if (err < 0)
+ goto out_free;
+ card->private_data = chip;
+ hda = container_of(chip, struct hda_intel, chip);
+
+ pci_set_drvdata(pci, card);
+
+ err = register_vga_switcheroo(chip);
+ if (err < 0) {
+ dev_err(card->dev, "Error registering vga_switcheroo client\n");
+ goto out_free;
+ }
+
+ if (check_hdmi_disabled(pci)) {
+ dev_info(card->dev, "VGA controller is disabled\n");
+ dev_info(card->dev, "Delaying initialization\n");
+ chip->disabled = true;
+ }
+
+ schedule_probe = !chip->disabled;
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ if (patch[dev] && *patch[dev]) {
+ dev_info(card->dev, "Applying patch firmware '%s'\n",
+ patch[dev]);
+ err = request_firmware_nowait(THIS_MODULE, true, patch[dev],
+ &pci->dev, GFP_KERNEL, card,
+ azx_firmware_cb);
+ if (err < 0)
+ goto out_free;
+ schedule_probe = false; /* continued in azx_firmware_cb() */
+ }
+#endif /* CONFIG_SND_HDA_PATCH_LOADER */
+
+#ifndef CONFIG_SND_HDA_I915
+ if (CONTROLLER_IN_GPU(pci))
+ dev_err(card->dev, "Haswell/Broadwell HDMI/DP must build in CONFIG_SND_HDA_I915\n");
+#endif
+
+ if (schedule_probe)
+ schedule_delayed_work(&hda->probe_work, 0);
+
+ dev++;
+ if (chip->disabled)
+ complete_all(&hda->probe_wait);
+ return 0;
+
+out_free:
+ snd_card_free(card);
+ return err;
+}
+
+#ifdef CONFIG_PM
+/* On some boards setting power_save to a non 0 value leads to clicking /
+ * popping sounds when ever we enter/leave powersaving mode. Ideally we would
+ * figure out how to avoid these sounds, but that is not always feasible.
+ * So we keep a list of devices where we disable powersaving as its known
+ * to causes problems on these devices.
+ */
+static const struct snd_pci_quirk power_save_denylist[] = {
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x1849, 0xc892, "Asrock B85M-ITX", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x1849, 0x0397, "Asrock N68C-S UCC", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x1849, 0x7662, "Asrock H81M-HDS", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x1043, 0x8733, "Asus Prime X370-Pro", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x1558, 0x6504, "Clevo W65_67SB", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x1028, 0x0497, "Dell Precision T3600", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ /* Note the P55A-UD3 and Z87-D3HP share the subsys id for the HDA dev */
+ SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte P55A-UD3 / Z87-D3HP", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */
+ SND_PCI_QUIRK(0x8086, 0x2040, "Intel DZ77BH-55K", 0),
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=199607 */
+ SND_PCI_QUIRK(0x8086, 0x2057, "Intel NUC5i7RYB", 0),
+ /* https://bugs.launchpad.net/bugs/1821663 */
+ SND_PCI_QUIRK(0x8086, 0x2064, "Intel SDP 8086:2064", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1520902 */
+ SND_PCI_QUIRK(0x8086, 0x2068, "Intel NUC7i3BNB", 0),
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=198611 */
+ SND_PCI_QUIRK(0x17aa, 0x2227, "Lenovo X1 Carbon 3rd Gen", 0),
+ SND_PCI_QUIRK(0x17aa, 0x316e, "Lenovo ThinkCentre M70q", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1689623 */
+ SND_PCI_QUIRK(0x17aa, 0x367b, "Lenovo IdeaCentre B550", 0),
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1572975 */
+ SND_PCI_QUIRK(0x17aa, 0x36a7, "Lenovo C50 All in one", 0),
+ /* https://bugs.launchpad.net/bugs/1821663 */
+ SND_PCI_QUIRK(0x1631, 0xe017, "Packard Bell NEC IMEDIA 5204", 0),
+ /* KONTRON SinglePC may cause a stall at runtime resume */
+ SND_PCI_QUIRK(0x1734, 0x1232, "KONTRON SinglePC", 0),
+ {}
+};
+#endif /* CONFIG_PM */
+
+static void set_default_power_save(struct azx *chip)
+{
+ int val = power_save;
+
+#ifdef CONFIG_PM
+ if (pm_blacklist) {
+ const struct snd_pci_quirk *q;
+
+ q = snd_pci_quirk_lookup(chip->pci, power_save_denylist);
+ if (q && val) {
+ dev_info(chip->card->dev, "device %04x:%04x is on the power_save denylist, forcing power_save to 0\n",
+ q->subvendor, q->subdevice);
+ val = 0;
+ }
+ }
+#endif /* CONFIG_PM */
+ snd_hda_set_power_save(&chip->bus, val * 1000);
+}
+
+/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */
+static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = {
+ [AZX_DRIVER_NVIDIA] = 8,
+ [AZX_DRIVER_TERA] = 1,
+};
+
+static int azx_probe_continue(struct azx *chip)
+{
+ struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hdac_bus *bus = azx_bus(chip);
+ struct pci_dev *pci = chip->pci;
+ int dev = chip->dev_index;
+ int err;
+
+ if (chip->disabled || hda->init_failed)
+ return -EIO;
+ if (hda->probe_retry)
+ goto probe_retry;
+
+ to_hda_bus(bus)->bus_probing = 1;
+ hda->probe_continued = 1;
+
+ /* bind with i915 if needed */
+ if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT) {
+ err = snd_hdac_i915_init(bus);
+ if (err < 0) {
+ /* if the controller is bound only with HDMI/DP
+ * (for HSW and BDW), we need to abort the probe;
+ * for other chips, still continue probing as other
+ * codecs can be on the same link.
+ */
+ if (CONTROLLER_IN_GPU(pci)) {
+ dev_err(chip->card->dev,
+ "HSW/BDW HD-audio HDMI/DP requires binding with gfx driver\n");
+ goto out_free;
+ } else {
+ /* don't bother any longer */
+ chip->driver_caps &= ~AZX_DCAPS_I915_COMPONENT;
+ }
+ }
+
+ /* HSW/BDW controllers need this power */
+ if (CONTROLLER_IN_GPU(pci))
+ hda->need_i915_power = 1;
+ }
+
+ /* Request display power well for the HDA controller or codec. For
+ * Haswell/Broadwell, both the display HDA controller and codec need
+ * this power. For other platforms, like Baytrail/Braswell, only the
+ * display codec needs the power and it can be released after probe.
+ */
+ display_power(chip, true);
+
+ err = azx_first_init(chip);
+ if (err < 0)
+ goto out_free;
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+ chip->beep_mode = beep_mode[dev];
+#endif
+
+ /* create codec instances */
+ if (bus->codec_mask) {
+ err = azx_probe_codecs(chip, azx_max_codecs[chip->driver_type]);
+ if (err < 0)
+ goto out_free;
+ }
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ if (chip->fw) {
+ err = snd_hda_load_patch(&chip->bus, chip->fw->size,
+ chip->fw->data);
+ if (err < 0)
+ goto out_free;
+#ifndef CONFIG_PM
+ release_firmware(chip->fw); /* no longer needed */
+ chip->fw = NULL;
+#endif
+ }
+#endif
+
+ probe_retry:
+ if (bus->codec_mask && !(probe_only[dev] & 1)) {
+ err = azx_codec_configure(chip);
+ if (err) {
+ if ((chip->driver_caps & AZX_DCAPS_RETRY_PROBE) &&
+ ++hda->probe_retry < 60) {
+ schedule_delayed_work(&hda->probe_work,
+ msecs_to_jiffies(1000));
+ return 0; /* keep things up */
+ }
+ dev_err(chip->card->dev, "Cannot probe codecs, giving up\n");
+ goto out_free;
+ }
+ }
+
+ err = snd_card_register(chip->card);
+ if (err < 0)
+ goto out_free;
+
+ setup_vga_switcheroo_runtime_pm(chip);
+
+ chip->running = 1;
+ azx_add_card_list(chip);
+
+ set_default_power_save(chip);
+
+ if (azx_has_pm_runtime(chip)) {
+ pm_runtime_use_autosuspend(&pci->dev);
+ pm_runtime_allow(&pci->dev);
+ pm_runtime_put_autosuspend(&pci->dev);
+ }
+
+out_free:
+ if (err < 0) {
+ pci_set_drvdata(pci, NULL);
+ snd_card_free(chip->card);
+ return err;
+ }
+
+ if (!hda->need_i915_power)
+ display_power(chip, false);
+ complete_all(&hda->probe_wait);
+ to_hda_bus(bus)->bus_probing = 0;
+ hda->probe_retry = 0;
+ return 0;
+}
+
+static void azx_remove(struct pci_dev *pci)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct azx *chip;
+ struct hda_intel *hda;
+
+ if (card) {
+ /* cancel the pending probing work */
+ chip = card->private_data;
+ hda = container_of(chip, struct hda_intel, chip);
+ /* FIXME: below is an ugly workaround.
+ * Both device_release_driver() and driver_probe_device()
+ * take *both* the device's and its parent's lock before
+ * calling the remove() and probe() callbacks. The codec
+ * probe takes the locks of both the codec itself and its
+ * parent, i.e. the PCI controller dev. Meanwhile, when
+ * the PCI controller is unbound, it takes its lock, too
+ * ==> ouch, a deadlock!
+ * As a workaround, we unlock temporarily here the controller
+ * device during cancel_work_sync() call.
+ */
+ device_unlock(&pci->dev);
+ cancel_delayed_work_sync(&hda->probe_work);
+ device_lock(&pci->dev);
+
+ snd_card_free(card);
+ }
+}
+
+static void azx_shutdown(struct pci_dev *pci)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct azx *chip;
+
+ if (!card)
+ return;
+ chip = card->private_data;
+ if (chip && chip->running)
+ __azx_shutdown_chip(chip, true);
+}
+
+/* PCI IDs */
+static const struct pci_device_id azx_ids[] = {
+ /* CPT */
+ { PCI_DEVICE(0x8086, 0x1c20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM },
+ /* PBG */
+ { PCI_DEVICE(0x8086, 0x1d20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM },
+ /* Panther Point */
+ { PCI_DEVICE(0x8086, 0x1e20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM },
+ /* Lynx Point */
+ { PCI_DEVICE(0x8086, 0x8c20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* 9 Series */
+ { PCI_DEVICE(0x8086, 0x8ca0),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* Wellsburg */
+ { PCI_DEVICE(0x8086, 0x8d20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ { PCI_DEVICE(0x8086, 0x8d21),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* Lewisburg */
+ { PCI_DEVICE(0x8086, 0xa1f0),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_SKYLAKE },
+ { PCI_DEVICE(0x8086, 0xa270),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_SKYLAKE },
+ /* Lynx Point-LP */
+ { PCI_DEVICE(0x8086, 0x9c20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* Lynx Point-LP */
+ { PCI_DEVICE(0x8086, 0x9c21),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* Wildcat Point-LP */
+ { PCI_DEVICE(0x8086, 0x9ca0),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* Sunrise Point */
+ { PCI_DEVICE(0x8086, 0xa170),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE },
+ /* Sunrise Point-LP */
+ { PCI_DEVICE(0x8086, 0x9d70),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE },
+ /* Kabylake */
+ { PCI_DEVICE(0x8086, 0xa171),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE },
+ /* Kabylake-LP */
+ { PCI_DEVICE(0x8086, 0x9d71),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE },
+ /* Kabylake-H */
+ { PCI_DEVICE(0x8086, 0xa2f0),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE },
+ /* Coffelake */
+ { PCI_DEVICE(0x8086, 0xa348),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Cannonlake */
+ { PCI_DEVICE(0x8086, 0x9dc8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* CometLake-LP */
+ { PCI_DEVICE(0x8086, 0x02C8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* CometLake-H */
+ { PCI_DEVICE(0x8086, 0x06C8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ { PCI_DEVICE(0x8086, 0xf1c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* CometLake-S */
+ { PCI_DEVICE(0x8086, 0xa3f0),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* CometLake-R */
+ { PCI_DEVICE(0x8086, 0xf0c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Icelake */
+ { PCI_DEVICE(0x8086, 0x34c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Icelake-H */
+ { PCI_DEVICE(0x8086, 0x3dc8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Jasperlake */
+ { PCI_DEVICE(0x8086, 0x38c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ { PCI_DEVICE(0x8086, 0x4dc8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Tigerlake */
+ { PCI_DEVICE(0x8086, 0xa0c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Tigerlake-H */
+ { PCI_DEVICE(0x8086, 0x43c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* DG1 */
+ { PCI_DEVICE(0x8086, 0x490d),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* DG2 */
+ { PCI_DEVICE(0x8086, 0x4f90),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ { PCI_DEVICE(0x8086, 0x4f91),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ { PCI_DEVICE(0x8086, 0x4f92),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Alderlake-S */
+ { PCI_DEVICE(0x8086, 0x7ad0),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Alderlake-P */
+ { PCI_DEVICE(0x8086, 0x51c8),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Elkhart Lake */
+ { PCI_DEVICE(0x8086, 0x4b55),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ { PCI_DEVICE(0x8086, 0x4b58),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Broxton-P(Apollolake) */
+ { PCI_DEVICE(0x8086, 0x5a98),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON },
+ /* Broxton-T */
+ { PCI_DEVICE(0x8086, 0x1a98),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON },
+ /* Gemini-Lake */
+ { PCI_DEVICE(0x8086, 0x3198),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON },
+ /* Haswell */
+ { PCI_DEVICE(0x8086, 0x0a0c),
+ .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL },
+ { PCI_DEVICE(0x8086, 0x0c0c),
+ .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL },
+ { PCI_DEVICE(0x8086, 0x0d0c),
+ .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL },
+ /* Broadwell */
+ { PCI_DEVICE(0x8086, 0x160c),
+ .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_BROADWELL },
+ /* 5 Series/3400 */
+ { PCI_DEVICE(0x8086, 0x3b56),
+ .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM },
+ { PCI_DEVICE(0x8086, 0x3b57),
+ .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM },
+ /* Poulsbo */
+ { PCI_DEVICE(0x8086, 0x811b),
+ .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_BASE |
+ AZX_DCAPS_POSFIX_LPIB },
+ /* Oaktrail */
+ { PCI_DEVICE(0x8086, 0x080a),
+ .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_BASE },
+ /* BayTrail */
+ { PCI_DEVICE(0x8086, 0x0f04),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_BAYTRAIL },
+ /* Braswell */
+ { PCI_DEVICE(0x8086, 0x2284),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_BRASWELL },
+ /* ICH6 */
+ { PCI_DEVICE(0x8086, 0x2668),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ICH7 */
+ { PCI_DEVICE(0x8086, 0x27d8),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ESB2 */
+ { PCI_DEVICE(0x8086, 0x269a),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ICH8 */
+ { PCI_DEVICE(0x8086, 0x284b),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ICH9 */
+ { PCI_DEVICE(0x8086, 0x293e),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ICH9 */
+ { PCI_DEVICE(0x8086, 0x293f),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ICH10 */
+ { PCI_DEVICE(0x8086, 0x3a3e),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* ICH10 */
+ { PCI_DEVICE(0x8086, 0x3a6e),
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH },
+ /* Generic Intel */
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ANY_ID),
+ .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+ .class_mask = 0xffffff,
+ .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_NO_ALIGN_BUFSIZE },
+ /* ATI SB 450/600/700/800/900 */
+ { PCI_DEVICE(0x1002, 0x437b),
+ .driver_data = AZX_DRIVER_ATI | AZX_DCAPS_PRESET_ATI_SB },
+ { PCI_DEVICE(0x1002, 0x4383),
+ .driver_data = AZX_DRIVER_ATI | AZX_DCAPS_PRESET_ATI_SB },
+ /* AMD Hudson */
+ { PCI_DEVICE(0x1022, 0x780d),
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB },
+ /* AMD, X370 & co */
+ { PCI_DEVICE(0x1022, 0x1457),
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_AMD_SB },
+ /* AMD, X570 & co */
+ { PCI_DEVICE(0x1022, 0x1487),
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_AMD_SB },
+ /* AMD Stoney */
+ { PCI_DEVICE(0x1022, 0x157a),
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB |
+ AZX_DCAPS_PM_RUNTIME },
+ /* AMD Raven */
+ { PCI_DEVICE(0x1022, 0x15e3),
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_AMD_SB },
+ /* ATI HDMI */
+ { PCI_DEVICE(0x1002, 0x0002),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0x1308),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0x157a),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0x15b3),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0x793b),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0x7919),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0x960f),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0x970f),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0x9840),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaa00),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa08),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa10),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa18),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa20),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa28),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa30),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa38),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa40),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa48),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa50),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa58),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa60),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa68),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa80),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa88),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa90),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0xaa98),
+ .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(0x1002, 0x9902),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaaa0),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaaa8),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaab0),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaac0),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaac8),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
+ { PCI_DEVICE(0x1002, 0xaad8),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xaae0),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xaae8),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xaaf0),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xaaf8),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab00),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab08),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab10),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab18),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab20),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab28),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab30),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ { PCI_DEVICE(0x1002, 0xab38),
+ .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS |
+ AZX_DCAPS_PM_RUNTIME },
+ /* GLENFLY */
+ { PCI_DEVICE(0x6766, PCI_ANY_ID),
+ .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+ .class_mask = 0xffffff,
+ .driver_data = AZX_DRIVER_GFHDMI | AZX_DCAPS_POSFIX_LPIB |
+ AZX_DCAPS_NO_MSI | AZX_DCAPS_NO_64BIT },
+ /* VIA VT8251/VT8237A */
+ { PCI_DEVICE(0x1106, 0x3288), .driver_data = AZX_DRIVER_VIA },
+ /* VIA GFX VT7122/VX900 */
+ { PCI_DEVICE(0x1106, 0x9170), .driver_data = AZX_DRIVER_GENERIC },
+ /* VIA GFX VT6122/VX11 */
+ { PCI_DEVICE(0x1106, 0x9140), .driver_data = AZX_DRIVER_GENERIC },
+ /* SIS966 */
+ { PCI_DEVICE(0x1039, 0x7502), .driver_data = AZX_DRIVER_SIS },
+ /* ULI M5461 */
+ { PCI_DEVICE(0x10b9, 0x5461), .driver_data = AZX_DRIVER_ULI },
+ /* NVIDIA MCP */
+ { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID),
+ .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+ .class_mask = 0xffffff,
+ .driver_data = AZX_DRIVER_NVIDIA | AZX_DCAPS_PRESET_NVIDIA },
+ /* Teradici */
+ { PCI_DEVICE(0x6549, 0x1200),
+ .driver_data = AZX_DRIVER_TERA | AZX_DCAPS_NO_64BIT },
+ { PCI_DEVICE(0x6549, 0x2200),
+ .driver_data = AZX_DRIVER_TERA | AZX_DCAPS_NO_64BIT },
+ /* Creative X-Fi (CA0110-IBG) */
+ /* CTHDA chips */
+ { PCI_DEVICE(0x1102, 0x0010),
+ .driver_data = AZX_DRIVER_CTHDA | AZX_DCAPS_PRESET_CTHDA },
+ { PCI_DEVICE(0x1102, 0x0012),
+ .driver_data = AZX_DRIVER_CTHDA | AZX_DCAPS_PRESET_CTHDA },
+#if !IS_ENABLED(CONFIG_SND_CTXFI)
+ /* the following entry conflicts with snd-ctxfi driver,
+ * as ctxfi driver mutates from HD-audio to native mode with
+ * a special command sequence.
+ */
+ { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_ANY_ID),
+ .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+ .class_mask = 0xffffff,
+ .driver_data = AZX_DRIVER_CTX | AZX_DCAPS_CTX_WORKAROUND |
+ AZX_DCAPS_NO_64BIT | AZX_DCAPS_POSFIX_LPIB },
+#else
+ /* this entry seems still valid -- i.e. without emu20kx chip */
+ { PCI_DEVICE(0x1102, 0x0009),
+ .driver_data = AZX_DRIVER_CTX | AZX_DCAPS_CTX_WORKAROUND |
+ AZX_DCAPS_NO_64BIT | AZX_DCAPS_POSFIX_LPIB },
+#endif
+ /* CM8888 */
+ { PCI_DEVICE(0x13f6, 0x5011),
+ .driver_data = AZX_DRIVER_CMEDIA |
+ AZX_DCAPS_NO_MSI | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_SNOOP_OFF },
+ /* Vortex86MX */
+ { PCI_DEVICE(0x17f3, 0x3010), .driver_data = AZX_DRIVER_GENERIC },
+ /* VMware HDAudio */
+ { PCI_DEVICE(0x15ad, 0x1977), .driver_data = AZX_DRIVER_GENERIC },
+ /* AMD/ATI Generic, PCI class code and Vendor ID for HD Audio */
+ { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_ANY_ID),
+ .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+ .class_mask = 0xffffff,
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_ANY_ID),
+ .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+ .class_mask = 0xffffff,
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI },
+ /* Zhaoxin */
+ { PCI_DEVICE(0x1d17, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, azx_ids);
+
+/* pci_driver definition */
+static struct pci_driver azx_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = azx_ids,
+ .probe = azx_probe,
+ .remove = azx_remove,
+ .shutdown = azx_shutdown,
+ .driver = {
+ .pm = AZX_PM_OPS,
+ },
+};
+
+module_pci_driver(azx_driver);
diff --git a/sound/pci/hda/hda_intel.h b/sound/pci/hda/hda_intel.h
new file mode 100644
index 000000000..0f39418f9
--- /dev/null
+++ b/sound/pci/hda/hda_intel.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ */
+#ifndef __SOUND_HDA_INTEL_H
+#define __SOUND_HDA_INTEL_H
+
+#include "hda_controller.h"
+
+struct hda_intel {
+ struct azx chip;
+
+ /* for pending irqs */
+ struct work_struct irq_pending_work;
+
+ /* sync probing */
+ struct completion probe_wait;
+ struct delayed_work probe_work;
+
+ /* card list (for power_save trigger) */
+ struct list_head list;
+
+ /* extra flags */
+ unsigned int irq_pending_warned:1;
+ unsigned int probe_continued:1;
+
+ /* vga_switcheroo setup */
+ unsigned int use_vga_switcheroo:1;
+ unsigned int vga_switcheroo_registered:1;
+ unsigned int init_failed:1; /* delayed init failed */
+ unsigned int freed:1; /* resources already released */
+
+ bool need_i915_power:1; /* the hda controller needs i915 power */
+
+ int probe_retry; /* being probe-retry */
+};
+
+#endif
diff --git a/sound/pci/hda/hda_intel_trace.h b/sound/pci/hda/hda_intel_trace.h
new file mode 100644
index 000000000..73a7adfa1
--- /dev/null
+++ b/sound/pci/hda/hda_intel_trace.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM hda_intel
+#define TRACE_INCLUDE_FILE hda_intel_trace
+
+#if !defined(_TRACE_HDA_INTEL_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_HDA_INTEL_H
+
+#include <linux/tracepoint.h>
+
+DECLARE_EVENT_CLASS(hda_pm,
+ TP_PROTO(struct azx *chip),
+
+ TP_ARGS(chip),
+
+ TP_STRUCT__entry(
+ __field(int, dev_index)
+ ),
+
+ TP_fast_assign(
+ __entry->dev_index = (chip)->dev_index;
+ ),
+
+ TP_printk("card index: %d", __entry->dev_index)
+);
+
+DEFINE_EVENT(hda_pm, azx_suspend,
+ TP_PROTO(struct azx *chip),
+ TP_ARGS(chip)
+);
+
+DEFINE_EVENT(hda_pm, azx_resume,
+ TP_PROTO(struct azx *chip),
+ TP_ARGS(chip)
+);
+
+#ifdef CONFIG_PM
+DEFINE_EVENT(hda_pm, azx_runtime_suspend,
+ TP_PROTO(struct azx *chip),
+ TP_ARGS(chip)
+);
+
+DEFINE_EVENT(hda_pm, azx_runtime_resume,
+ TP_PROTO(struct azx *chip),
+ TP_ARGS(chip)
+);
+#endif
+
+#endif /* _TRACE_HDA_INTEL_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>
diff --git a/sound/pci/hda/hda_jack.c b/sound/pci/hda/hda_jack.c
new file mode 100644
index 000000000..588059428
--- /dev/null
+++ b/sound/pci/hda/hda_jack.c
@@ -0,0 +1,688 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Jack-detection handling for HD-audio
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/jack.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+
+/**
+ * is_jack_detectable - Check whether the given pin is jack-detectable
+ * @codec: the HDA codec
+ * @nid: pin NID
+ *
+ * Check whether the given pin is capable to report the jack detection.
+ * The jack detection might not work by various reasons, e.g. the jack
+ * detection is prohibited in the codec level, the pin config has
+ * AC_DEFCFG_MISC_NO_PRESENCE bit, no unsol support, etc.
+ */
+bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid)
+{
+ if (codec->no_jack_detect)
+ return false;
+ if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_PRES_DETECT))
+ return false;
+ if (get_defcfg_misc(snd_hda_codec_get_pincfg(codec, nid)) &
+ AC_DEFCFG_MISC_NO_PRESENCE)
+ return false;
+ if (!(get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) &&
+ !codec->jackpoll_interval)
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(is_jack_detectable);
+
+/* execute pin sense measurement */
+static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ u32 pincap;
+ u32 val;
+
+ if (!codec->no_trigger_sense) {
+ pincap = snd_hda_query_pin_caps(codec, nid);
+ if (pincap & AC_PINCAP_TRIG_REQ) /* need trigger? */
+ snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_SET_PIN_SENSE, 0);
+ }
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_SENSE, dev_id);
+ if (codec->inv_jack_detect)
+ val ^= AC_PINSENSE_PRESENCE;
+ return val;
+}
+
+/**
+ * snd_hda_jack_tbl_get_mst - query the jack-table entry for the given NID
+ * @codec: the HDA codec
+ * @nid: pin NID to refer to
+ * @dev_id: pin device entry id
+ */
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ if (!nid || !jack)
+ return NULL;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid == nid && jack->dev_id == dev_id)
+ return jack;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_mst);
+
+/**
+ * snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag
+ * @codec: the HDA codec
+ * @tag: tag value to refer to
+ * @dev_id: pin device entry id
+ */
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
+ unsigned char tag, int dev_id)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ if (!tag || !jack)
+ return NULL;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->tag == tag && jack->dev_id == dev_id)
+ return jack;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
+
+static struct hda_jack_tbl *
+any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ if (!nid || !jack)
+ return NULL;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid == nid)
+ return jack;
+ return NULL;
+}
+
+/**
+ * snd_hda_jack_tbl_new - create a jack-table entry for the given NID
+ * @codec: the HDA codec
+ * @nid: pin NID to assign
+ * @dev_id: pin device entry id
+ */
+static struct hda_jack_tbl *
+snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack =
+ snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ struct hda_jack_tbl *existing_nid_jack =
+ any_jack_tbl_get_from_nid(codec, nid);
+
+ WARN_ON(dev_id != 0 && !codec->dp_mst);
+
+ if (jack)
+ return jack;
+ jack = snd_array_new(&codec->jacktbl);
+ if (!jack)
+ return NULL;
+ jack->nid = nid;
+ jack->dev_id = dev_id;
+ jack->jack_dirty = 1;
+ if (existing_nid_jack) {
+ jack->tag = existing_nid_jack->tag;
+
+ /*
+ * Copy jack_detect from existing_nid_jack to avoid
+ * snd_hda_jack_detect_enable_callback_mst() making multiple
+ * SET_UNSOLICITED_ENABLE calls on the same pin.
+ */
+ jack->jack_detect = existing_nid_jack->jack_detect;
+ } else {
+ jack->tag = codec->jacktbl.used;
+ }
+
+ return jack;
+}
+
+void snd_hda_jack_tbl_clear(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ struct hda_jack_callback *cb, *next;
+
+ /* free jack instances manually when clearing/reconfiguring */
+ if (!codec->bus->shutdown && jack->jack)
+ snd_device_free(codec->card, jack->jack);
+
+ for (cb = jack->callback; cb; cb = next) {
+ next = cb->next;
+ kfree(cb);
+ }
+ }
+ snd_array_free(&codec->jacktbl);
+}
+
+#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE)
+
+/* update the cached value and notification flag if needed */
+static void jack_detect_update(struct hda_codec *codec,
+ struct hda_jack_tbl *jack)
+{
+ if (!jack->jack_dirty)
+ return;
+
+ if (jack->phantom_jack)
+ jack->pin_sense = AC_PINSENSE_PRESENCE;
+ else
+ jack->pin_sense = read_pin_sense(codec, jack->nid,
+ jack->dev_id);
+
+ /* A gating jack indicates the jack is invalid if gating is unplugged */
+ if (jack->gating_jack &&
+ !snd_hda_jack_detect_mst(codec, jack->gating_jack, jack->dev_id))
+ jack->pin_sense &= ~AC_PINSENSE_PRESENCE;
+
+ jack->jack_dirty = 0;
+
+ /* If a jack is gated by this one update it. */
+ if (jack->gated_jack) {
+ struct hda_jack_tbl *gated =
+ snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
+ jack->dev_id);
+ if (gated) {
+ gated->jack_dirty = 1;
+ jack_detect_update(codec, gated);
+ }
+ }
+}
+
+/**
+ * snd_hda_set_dirty_all - Mark all the cached as dirty
+ * @codec: the HDA codec
+ *
+ * This function sets the dirty flag to all entries of jack table.
+ * It's called from the resume path in hda_codec.c.
+ */
+void snd_hda_jack_set_dirty_all(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid)
+ jack->jack_dirty = 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_set_dirty_all);
+
+/**
+ * snd_hda_jack_pin_sense - execute pin sense measurement
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ * @dev_id: pin device entry id
+ *
+ * Execute necessary pin sense measurement and return its Presence Detect,
+ * Impedance, ELD Valid etc. status bits.
+ */
+u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack =
+ snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ if (jack) {
+ jack_detect_update(codec, jack);
+ return jack->pin_sense;
+ }
+ return read_pin_sense(codec, nid, dev_id);
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_pin_sense);
+
+/**
+ * snd_hda_jack_detect_state_mst - query pin Presence Detect status
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ * @dev_id: pin device entry id
+ *
+ * Query and return the pin's Presence Detect status, as either
+ * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
+ */
+int snd_hda_jack_detect_state_mst(struct hda_codec *codec,
+ hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack =
+ snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ if (jack && jack->phantom_jack)
+ return HDA_JACK_PHANTOM;
+ else if (snd_hda_jack_pin_sense(codec, nid, dev_id) &
+ AC_PINSENSE_PRESENCE)
+ return HDA_JACK_PRESENT;
+ else
+ return HDA_JACK_NOT_PRESENT;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state_mst);
+
+static struct hda_jack_callback *
+find_callback_from_list(struct hda_jack_tbl *jack,
+ hda_jack_callback_fn func)
+{
+ struct hda_jack_callback *cb;
+
+ if (!func)
+ return NULL;
+
+ for (cb = jack->callback; cb; cb = cb->next) {
+ if (cb->func == func)
+ return cb;
+ }
+
+ return NULL;
+}
+
+/**
+ * snd_hda_jack_detect_enable_mst - enable the jack-detection
+ * @codec: the HDA codec
+ * @nid: pin NID to enable
+ * @func: callback function to register
+ * @dev_id: pin device entry id
+ *
+ * In the case of error, the return value will be a pointer embedded with
+ * errno. Check and handle the return value appropriately with standard
+ * macros such as @IS_ERR() and @PTR_ERR().
+ */
+struct hda_jack_callback *
+snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, hda_jack_callback_fn func)
+{
+ struct hda_jack_tbl *jack;
+ struct hda_jack_callback *callback = NULL;
+ int err;
+
+ jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
+ if (!jack)
+ return ERR_PTR(-ENOMEM);
+
+ callback = find_callback_from_list(jack, func);
+
+ if (func && !callback) {
+ callback = kzalloc(sizeof(*callback), GFP_KERNEL);
+ if (!callback)
+ return ERR_PTR(-ENOMEM);
+ callback->func = func;
+ callback->nid = jack->nid;
+ callback->dev_id = jack->dev_id;
+ callback->next = jack->callback;
+ jack->callback = callback;
+ }
+
+ if (jack->jack_detect)
+ return callback; /* already registered */
+ jack->jack_detect = 1;
+ if (codec->jackpoll_interval > 0)
+ return callback; /* No unsol if we're polling instead */
+ err = snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE,
+ AC_USRSP_EN | jack->tag);
+ if (err < 0)
+ return ERR_PTR(err);
+ return callback;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback_mst);
+
+/**
+ * snd_hda_jack_detect_enable - Enable the jack detection on the given pin
+ * @codec: the HDA codec
+ * @nid: pin NID to enable jack detection
+ * @dev_id: pin device entry id
+ *
+ * Enable the jack detection with the default callback. Returns zero if
+ * successful or a negative error code.
+ */
+int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id)
+{
+ return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback_mst(codec,
+ nid,
+ dev_id,
+ NULL));
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
+
+/**
+ * snd_hda_jack_set_gating_jack - Set gating jack.
+ * @codec: the HDA codec
+ * @gated_nid: gated pin NID
+ * @gating_nid: gating pin NID
+ *
+ * Indicates the gated jack is only valid when the gating jack is plugged.
+ */
+int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
+ hda_nid_t gating_nid)
+{
+ struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0);
+ struct hda_jack_tbl *gating =
+ snd_hda_jack_tbl_new(codec, gating_nid, 0);
+
+ WARN_ON(codec->dp_mst);
+
+ if (!gated || !gating)
+ return -EINVAL;
+
+ gated->gating_jack = gating_nid;
+ gating->gated_jack = gated_nid;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_set_gating_jack);
+
+/**
+ * snd_hda_jack_report_sync - sync the states of all jacks and report if changed
+ * @codec: the HDA codec
+ */
+void snd_hda_jack_report_sync(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack;
+ int i, state;
+
+ /* update all jacks at first */
+ jack = codec->jacktbl.list;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid)
+ jack_detect_update(codec, jack);
+
+ /* report the updated jacks; it's done after updating all jacks
+ * to make sure that all gating jacks properly have been set
+ */
+ jack = codec->jacktbl.list;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid) {
+ if (!jack->jack || jack->block_report)
+ continue;
+ state = jack->button_state;
+ if (get_jack_plug_state(jack->pin_sense))
+ state |= jack->type;
+ snd_jack_report(jack->jack, state);
+ if (jack->button_state) {
+ snd_jack_report(jack->jack,
+ state & ~jack->button_state);
+ jack->button_state = 0; /* button released */
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_report_sync);
+
+/* guess the jack type from the pin-config */
+static int get_input_jack_type(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_LINE_OUT:
+ case AC_JACK_SPEAKER:
+ return SND_JACK_LINEOUT;
+ case AC_JACK_HP_OUT:
+ return SND_JACK_HEADPHONE;
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ return SND_JACK_AVOUT;
+ case AC_JACK_MIC_IN:
+ return SND_JACK_MICROPHONE;
+ default:
+ return SND_JACK_LINEIN;
+ }
+}
+
+static void hda_free_jack_priv(struct snd_jack *jack)
+{
+ struct hda_jack_tbl *jacks = jack->private_data;
+ jacks->nid = 0;
+ jacks->jack = NULL;
+}
+
+/**
+ * snd_hda_jack_add_kctl_mst - Add a kctl for the given pin
+ * @codec: the HDA codec
+ * @nid: pin NID to assign
+ * @dev_id : pin device entry id
+ * @name: string name for the jack
+ * @phantom_jack: flag to deal as a phantom jack
+ * @type: jack type bits to be reported, 0 for guessing from pincfg
+ * @keymap: optional jack / key mapping
+ *
+ * This assigns a jack-detection kctl to the given pin. The kcontrol
+ * will have the given name and index.
+ */
+int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, const char *name, bool phantom_jack,
+ int type, const struct hda_jack_keymap *keymap)
+{
+ struct hda_jack_tbl *jack;
+ const struct hda_jack_keymap *map;
+ int err, state, buttons;
+
+ jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
+ if (!jack)
+ return 0;
+ if (jack->jack)
+ return 0; /* already created */
+
+ if (!type)
+ type = get_input_jack_type(codec, nid);
+
+ buttons = 0;
+ if (keymap) {
+ for (map = keymap; map->type; map++)
+ buttons |= map->type;
+ }
+
+ err = snd_jack_new(codec->card, name, type | buttons,
+ &jack->jack, true, phantom_jack);
+ if (err < 0)
+ return err;
+
+ jack->phantom_jack = !!phantom_jack;
+ jack->type = type;
+ jack->button_state = 0;
+ jack->jack->private_data = jack;
+ jack->jack->private_free = hda_free_jack_priv;
+ if (keymap) {
+ for (map = keymap; map->type; map++)
+ snd_jack_set_key(jack->jack, map->type, map->key);
+ }
+
+ state = snd_hda_jack_detect_mst(codec, nid, dev_id);
+ snd_jack_report(jack->jack, state ? jack->type : 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl_mst);
+
+static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ const char *base_name)
+{
+ unsigned int def_conf, conn;
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int err;
+ bool phantom_jack;
+
+ WARN_ON(codec->dp_mst);
+
+ if (!nid)
+ return 0;
+ def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ conn = get_defcfg_connect(def_conf);
+ if (conn == AC_JACK_PORT_NONE)
+ return 0;
+ phantom_jack = (conn != AC_JACK_PORT_COMPLEX) ||
+ !is_jack_detectable(codec, nid);
+
+ if (base_name)
+ strlcpy(name, base_name, sizeof(name));
+ else
+ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL);
+ if (phantom_jack)
+ /* Example final name: "Internal Mic Phantom Jack" */
+ strncat(name, " Phantom", sizeof(name) - strlen(name) - 1);
+ err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL);
+ if (err < 0)
+ return err;
+
+ if (!phantom_jack)
+ return snd_hda_jack_detect_enable(codec, nid, 0);
+ return 0;
+}
+
+/**
+ * snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg
+ * @codec: the HDA codec
+ * @cfg: pin config table to parse
+ */
+int snd_hda_jack_add_kctls(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg)
+{
+ const hda_nid_t *p;
+ int i, err;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ /* If we have headphone mics; make sure they get the right name
+ before grabbed by output pins */
+ if (cfg->inputs[i].is_headphone_mic) {
+ if (auto_cfg_hp_outs(cfg) == 1)
+ err = add_jack_kctl(codec, auto_cfg_hp_pins(cfg)[0],
+ cfg, "Headphone Mic");
+ else
+ err = add_jack_kctl(codec, cfg->inputs[i].pin,
+ cfg, "Headphone Mic");
+ } else
+ err = add_jack_kctl(codec, cfg->inputs[i].pin, cfg,
+ NULL);
+ if (err < 0)
+ return err;
+ }
+
+ for (i = 0, p = cfg->line_out_pins; i < cfg->line_outs; i++, p++) {
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ for (i = 0, p = cfg->hp_pins; i < cfg->hp_outs; i++, p++) {
+ if (*p == *cfg->line_out_pins) /* might be duplicated */
+ break;
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ for (i = 0, p = cfg->speaker_pins; i < cfg->speaker_outs; i++, p++) {
+ if (*p == *cfg->line_out_pins) /* might be duplicated */
+ break;
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ for (i = 0, p = cfg->dig_out_pins; i < cfg->dig_outs; i++, p++) {
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ err = add_jack_kctl(codec, cfg->dig_in_pin, cfg, NULL);
+ if (err < 0)
+ return err;
+ err = add_jack_kctl(codec, cfg->mono_out_pin, cfg, NULL);
+ if (err < 0)
+ return err;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctls);
+
+static void call_jack_callback(struct hda_codec *codec, unsigned int res,
+ struct hda_jack_tbl *jack)
+{
+ struct hda_jack_callback *cb;
+
+ for (cb = jack->callback; cb; cb = cb->next) {
+ cb->jack = jack;
+ cb->unsol_res = res;
+ cb->func(codec, cb);
+ }
+ if (jack->gated_jack) {
+ struct hda_jack_tbl *gated =
+ snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
+ jack->dev_id);
+ if (gated) {
+ for (cb = gated->callback; cb; cb = cb->next) {
+ cb->jack = gated;
+ cb->unsol_res = res;
+ cb->func(codec, cb);
+ }
+ }
+ }
+}
+
+/**
+ * snd_hda_jack_unsol_event - Handle an unsolicited event
+ * @codec: the HDA codec
+ * @res: the unsolicited event data
+ */
+void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+ struct hda_jack_tbl *event;
+ int tag = (res & AC_UNSOL_RES_TAG) >> AC_UNSOL_RES_TAG_SHIFT;
+
+ if (codec->dp_mst) {
+ int dev_entry =
+ (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
+
+ event = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
+ } else {
+ event = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
+ }
+ if (!event)
+ return;
+ event->jack_dirty = 1;
+
+ call_jack_callback(codec, res, event);
+ snd_hda_jack_report_sync(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event);
+
+/**
+ * snd_hda_jack_poll_all - Poll all jacks
+ * @codec: the HDA codec
+ *
+ * Poll all detectable jacks with dirty flag, update the status, call
+ * callbacks and call snd_hda_jack_report_sync() if any changes are found.
+ */
+void snd_hda_jack_poll_all(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i, changes = 0;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ unsigned int old_sense;
+ if (!jack->nid || !jack->jack_dirty || jack->phantom_jack)
+ continue;
+ old_sense = get_jack_plug_state(jack->pin_sense);
+ jack_detect_update(codec, jack);
+ if (old_sense == get_jack_plug_state(jack->pin_sense))
+ continue;
+ changes = 1;
+ call_jack_callback(codec, 0, jack);
+ }
+ if (changes)
+ snd_hda_jack_report_sync(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_poll_all);
+
diff --git a/sound/pci/hda/hda_jack.h b/sound/pci/hda/hda_jack.h
new file mode 100644
index 000000000..8ceaf0ef5
--- /dev/null
+++ b/sound/pci/hda/hda_jack.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Jack-detection handling for HD-audio
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_JACK_H
+#define __SOUND_HDA_JACK_H
+
+#include <linux/err.h>
+#include <sound/jack.h>
+
+struct auto_pin_cfg;
+struct hda_jack_tbl;
+struct hda_jack_callback;
+
+typedef void (*hda_jack_callback_fn) (struct hda_codec *, struct hda_jack_callback *);
+
+struct hda_jack_callback {
+ hda_nid_t nid;
+ int dev_id;
+ hda_jack_callback_fn func;
+ unsigned int private_data; /* arbitrary data */
+ unsigned int unsol_res; /* unsolicited event bits */
+ struct hda_jack_tbl *jack; /* associated jack entry */
+ struct hda_jack_callback *next;
+};
+
+struct hda_jack_tbl {
+ hda_nid_t nid;
+ int dev_id;
+ unsigned char tag; /* unsol event tag */
+ struct hda_jack_callback *callback;
+ /* jack-detection stuff */
+ unsigned int pin_sense; /* cached pin-sense value */
+ unsigned int jack_detect:1; /* capable of jack-detection? */
+ unsigned int jack_dirty:1; /* needs to update? */
+ unsigned int phantom_jack:1; /* a fixed, always present port? */
+ unsigned int block_report:1; /* in a transitional state - do not report to userspace */
+ hda_nid_t gating_jack; /* valid when gating jack plugged */
+ hda_nid_t gated_jack; /* gated is dependent on this jack */
+ int type;
+ int button_state;
+ struct snd_jack *jack;
+};
+
+struct hda_jack_keymap {
+ enum snd_jack_types type;
+ int key;
+};
+
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id);
+
+/**
+ * snd_hda_jack_tbl_get - query the jack-table entry for the given NID
+ * @codec: the HDA codec
+ * @nid: pin NID to refer to
+ */
+static inline struct hda_jack_tbl *
+snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_jack_tbl_get_mst(codec, nid, 0);
+}
+
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
+ unsigned char tag, int dev_id);
+
+void snd_hda_jack_tbl_clear(struct hda_codec *codec);
+
+void snd_hda_jack_set_dirty_all(struct hda_codec *codec);
+
+int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id);
+
+struct hda_jack_callback *
+snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, hda_jack_callback_fn func);
+
+/**
+ * snd_hda_jack_detect_enable - enable the jack-detection
+ * @codec: the HDA codec
+ * @nid: pin NID to enable
+ * @func: callback function to register
+ *
+ * In the case of error, the return value will be a pointer embedded with
+ * errno. Check and handle the return value appropriately with standard
+ * macros such as @IS_ERR() and @PTR_ERR().
+ */
+static inline struct hda_jack_callback *
+snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
+ hda_jack_callback_fn cb)
+{
+ return snd_hda_jack_detect_enable_callback_mst(codec, nid, 0, cb);
+}
+
+int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
+ hda_nid_t gating_nid);
+
+u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id);
+
+/* the jack state returned from snd_hda_jack_detect_state() */
+enum {
+ HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT, HDA_JACK_PHANTOM,
+};
+
+int snd_hda_jack_detect_state_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id);
+
+/**
+ * snd_hda_jack_detect_state - query pin Presence Detect status
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ *
+ * Query and return the pin's Presence Detect status, as either
+ * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
+ */
+static inline int
+snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_jack_detect_state_mst(codec, nid, 0);
+}
+
+/**
+ * snd_hda_jack_detect_mst - Detect the jack
+ * @codec: the HDA codec
+ * @nid: pin NID to check jack detection
+ * @dev_id: pin device entry id
+ */
+static inline bool
+snd_hda_jack_detect_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ return snd_hda_jack_detect_state_mst(codec, nid, dev_id) !=
+ HDA_JACK_NOT_PRESENT;
+}
+
+/**
+ * snd_hda_jack_detect - Detect the jack
+ * @codec: the HDA codec
+ * @nid: pin NID to check jack detection
+ */
+static inline bool
+snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_jack_detect_mst(codec, nid, 0);
+}
+
+bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid);
+
+int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, const char *name, bool phantom_jack,
+ int type, const struct hda_jack_keymap *keymap);
+
+/**
+ * snd_hda_jack_add_kctl - Add a kctl for the given pin
+ * @codec: the HDA codec
+ * @nid: pin NID to assign
+ * @name: string name for the jack
+ * @phantom_jack: flag to deal as a phantom jack
+ * @type: jack type bits to be reported, 0 for guessing from pincfg
+ * @keymap: optional jack / key mapping
+ *
+ * This assigns a jack-detection kctl to the given pin. The kcontrol
+ * will have the given name and index.
+ */
+static inline int
+snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
+ const char *name, bool phantom_jack,
+ int type, const struct hda_jack_keymap *keymap)
+{
+ return snd_hda_jack_add_kctl_mst(codec, nid, 0,
+ name, phantom_jack, type, keymap);
+}
+
+int snd_hda_jack_add_kctls(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg);
+
+void snd_hda_jack_report_sync(struct hda_codec *codec);
+
+void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res);
+
+void snd_hda_jack_poll_all(struct hda_codec *codec);
+
+#endif /* __SOUND_HDA_JACK_H */
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
new file mode 100644
index 000000000..efc0c68a5
--- /dev/null
+++ b/sound/pci/hda/hda_local.h
@@ -0,0 +1,732 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Local helper functions
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_LOCAL_H
+#define __SOUND_HDA_LOCAL_H
+
+/* We abuse kcontrol_new.subdev field to pass the NID corresponding to
+ * the given new control. If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG,
+ * snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID.
+ *
+ * Note that the subdevice field is cleared again before the real registration
+ * in snd_hda_ctl_add(), so that this value won't appear in the outside.
+ */
+#define HDA_SUBDEV_NID_FLAG (1U << 31)
+#define HDA_SUBDEV_AMP_FLAG (1U << 30)
+
+/*
+ * for mixer controls
+ */
+#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs) \
+ ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23))
+#define HDA_AMP_VAL_MIN_MUTE (1<<29)
+#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \
+ HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0)
+/* mono volume with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, dir, flags) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
+ .info = snd_hda_mixer_amp_volume_info, \
+ .get = snd_hda_mixer_amp_volume_get, \
+ .put = snd_hda_mixer_amp_volume_put, \
+ .tlv = { .c = snd_hda_mixer_amp_tlv }, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, dir) | flags }
+/* stereo volume with index */
+#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction, 0)
+/* mono volume */
+#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction, 0)
+/* stereo volume */
+#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
+/* stereo volume with min=mute */
+#define HDA_CODEC_VOLUME_MIN_MUTE(xname, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction, \
+ HDA_AMP_VAL_MIN_MUTE)
+/* mono mute switch with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .info = snd_hda_mixer_amp_switch_info, \
+ .get = snd_hda_mixer_amp_switch_get, \
+ .put = snd_hda_mixer_amp_switch_put, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+/* stereo mute switch with index */
+#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
+/* mono mute switch */
+#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+/* stereo mute switch */
+#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
+ HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .info = snd_hda_mixer_amp_switch_info, \
+ .get = snd_hda_mixer_amp_switch_get_beep, \
+ .put = snd_hda_mixer_amp_switch_put_beep, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+#else
+/* no digital beep - just the standard one */
+#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, ch, xidx, dir)
+#endif /* CONFIG_SND_HDA_INPUT_BEEP */
+/* special beep mono mute switch */
+#define HDA_CODEC_MUTE_BEEP_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+/* special beep stereo mute switch */
+#define HDA_CODEC_MUTE_BEEP(xname, nid, xindex, direction) \
+ HDA_CODEC_MUTE_BEEP_MONO(xname, nid, 3, xindex, direction)
+
+extern const char *snd_hda_pcm_type_name[];
+
+int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *_tlv);
+int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+#endif
+/* lowlevel accessor with caching; use carefully */
+#define snd_hda_codec_amp_read(codec, nid, ch, dir, idx) \
+ snd_hdac_regmap_get_amp(&(codec)->core, nid, ch, dir, idx)
+int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, int mask, int val);
+int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int direction, int idx, int mask, int val);
+int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
+ int direction, int idx, int mask, int val);
+int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx, int mask, int val);
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int *tlv);
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+ const char *name);
+int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+ unsigned int *tlv, const char * const *followers,
+ const char *suffix, bool init_follower_vol,
+ struct snd_kcontrol **ctl_ret);
+#define snd_hda_add_vmaster(codec, name, tlv, followers, suffix) \
+ __snd_hda_add_vmaster(codec, name, tlv, followers, suffix, true, NULL)
+int snd_hda_codec_reset(struct hda_codec *codec);
+void snd_hda_codec_register(struct hda_codec *codec);
+void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec);
+
+#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core)
+
+enum {
+ HDA_VMUTE_OFF,
+ HDA_VMUTE_ON,
+ HDA_VMUTE_FOLLOW_MASTER,
+};
+
+struct hda_vmaster_mute_hook {
+ /* below two fields must be filled by the caller of
+ * snd_hda_add_vmaster_hook() beforehand
+ */
+ struct snd_kcontrol *sw_kctl;
+ void (*hook)(void *, int);
+ /* below are initialized automatically */
+ unsigned int mute_mode; /* HDA_VMUTE_XXX */
+ struct hda_codec *codec;
+};
+
+int snd_hda_add_vmaster_hook(struct hda_codec *codec,
+ struct hda_vmaster_mute_hook *hook,
+ bool expose_enum_ctl);
+void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook);
+
+/* amp value bits */
+#define HDA_AMP_MUTE 0x80
+#define HDA_AMP_UNMUTE 0x00
+#define HDA_AMP_VOLMASK 0x7f
+
+/*
+ * SPDIF I/O
+ */
+int snd_hda_create_dig_out_ctls(struct hda_codec *codec,
+ hda_nid_t associated_nid,
+ hda_nid_t cvt_nid, int type);
+#define snd_hda_create_spdif_out_ctls(codec, anid, cnid) \
+ snd_hda_create_dig_out_ctls(codec, anid, cnid, HDA_PCM_TYPE_SPDIF)
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
+
+/*
+ * input MUX helper
+ */
+#define HDA_MAX_NUM_INPUTS 16
+struct hda_input_mux_item {
+ char label[32];
+ unsigned int index;
+};
+struct hda_input_mux {
+ unsigned int num_items;
+ struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
+};
+
+int snd_hda_input_mux_info(const struct hda_input_mux *imux,
+ struct snd_ctl_elem_info *uinfo);
+int snd_hda_input_mux_put(struct hda_codec *codec,
+ const struct hda_input_mux *imux,
+ struct snd_ctl_elem_value *ucontrol, hda_nid_t nid,
+ unsigned int *cur_val);
+int snd_hda_add_imux_item(struct hda_codec *codec,
+ struct hda_input_mux *imux, const char *label,
+ int index, int *type_idx);
+
+/*
+ * Multi-channel / digital-out PCM helper
+ */
+
+enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
+enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
+
+#define HDA_MAX_OUTS 5
+
+struct hda_multi_out {
+ int num_dacs; /* # of DACs, must be more than 1 */
+ const hda_nid_t *dac_nids; /* DAC list */
+ hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */
+ hda_nid_t hp_out_nid[HDA_MAX_OUTS]; /* DACs for multiple HPs */
+ hda_nid_t extra_out_nid[HDA_MAX_OUTS]; /* other (e.g. speaker) DACs */
+ hda_nid_t dig_out_nid; /* digital out audio widget */
+ const hda_nid_t *follower_dig_outs;
+ int max_channels; /* currently supported analog channels */
+ int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */
+ int no_share_stream; /* don't share a stream with multiple pins */
+ int share_spdif; /* share SPDIF pin */
+ /* PCM information for both analog and SPDIF DACs */
+ unsigned int analog_rates;
+ unsigned int analog_maxbps;
+ u64 analog_formats;
+ unsigned int spdif_rates;
+ unsigned int spdif_maxbps;
+ u64 spdif_formats;
+};
+
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_open(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_close(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream);
+int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_analog_open(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ struct snd_pcm_substream *substream,
+ struct hda_pcm_stream *hinfo);
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream);
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+
+/*
+ * generic proc interface
+ */
+#ifdef CONFIG_SND_PROC_FS
+int snd_hda_codec_proc_new(struct hda_codec *codec);
+#else
+static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
+#endif
+
+#define SND_PRINT_BITS_ADVISED_BUFSIZE 16
+void snd_print_pcm_bits(int pcm, char *buf, int buflen);
+
+/*
+ * Misc
+ */
+int snd_hda_add_new_ctls(struct hda_codec *codec,
+ const struct snd_kcontrol_new *knew);
+
+/*
+ * Fix-up pin default configurations and add default verbs
+ */
+
+struct hda_pintbl {
+ hda_nid_t nid;
+ u32 val;
+};
+
+struct hda_model_fixup {
+ const int id;
+ const char *name;
+};
+
+struct hda_fixup {
+ int type;
+ bool chained:1; /* call the chained fixup(s) after this */
+ bool chained_before:1; /* call the chained fixup(s) before this */
+ int chain_id;
+ union {
+ const struct hda_pintbl *pins;
+ const struct hda_verb *verbs;
+ void (*func)(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action);
+ } v;
+};
+
+struct snd_hda_pin_quirk {
+ unsigned int codec; /* Codec vendor/device ID */
+ unsigned short subvendor; /* PCI subvendor ID */
+ const struct hda_pintbl *pins; /* list of matching pins */
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ const char *name;
+#endif
+ int value; /* quirk value */
+};
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+
+#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
+ { .codec = _codec,\
+ .subvendor = _subvendor,\
+ .name = _name,\
+ .value = _value,\
+ .pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
+ }
+#else
+
+#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
+ { .codec = _codec,\
+ .subvendor = _subvendor,\
+ .value = _value,\
+ .pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
+ }
+
+#endif
+
+#define HDA_FIXUP_ID_NOT_SET -1
+#define HDA_FIXUP_ID_NO_FIXUP -2
+
+/* fixup types */
+enum {
+ HDA_FIXUP_INVALID,
+ HDA_FIXUP_PINS,
+ HDA_FIXUP_VERBS,
+ HDA_FIXUP_FUNC,
+ HDA_FIXUP_PINCTLS,
+};
+
+/* fixup action definitions */
+enum {
+ HDA_FIXUP_ACT_PRE_PROBE,
+ HDA_FIXUP_ACT_PROBE,
+ HDA_FIXUP_ACT_INIT,
+ HDA_FIXUP_ACT_BUILD,
+ HDA_FIXUP_ACT_FREE,
+};
+
+int snd_hda_add_verbs(struct hda_codec *codec, const struct hda_verb *list);
+void snd_hda_apply_verbs(struct hda_codec *codec);
+void snd_hda_apply_pincfgs(struct hda_codec *codec,
+ const struct hda_pintbl *cfg);
+void snd_hda_apply_fixup(struct hda_codec *codec, int action);
+void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth);
+void snd_hda_pick_fixup(struct hda_codec *codec,
+ const struct hda_model_fixup *models,
+ const struct snd_pci_quirk *quirk,
+ const struct hda_fixup *fixlist);
+void snd_hda_pick_pin_fixup(struct hda_codec *codec,
+ const struct snd_hda_pin_quirk *pin_quirk,
+ const struct hda_fixup *fixlist,
+ bool match_all_pins);
+
+/* helper macros to retrieve pin default-config values */
+#define get_defcfg_connect(cfg) \
+ ((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT)
+#define get_defcfg_association(cfg) \
+ ((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT)
+#define get_defcfg_location(cfg) \
+ ((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
+#define get_defcfg_sequence(cfg) \
+ (cfg & AC_DEFCFG_SEQUENCE)
+#define get_defcfg_device(cfg) \
+ ((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
+#define get_defcfg_misc(cfg) \
+ ((cfg & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT)
+
+/* amp values */
+#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8))
+#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8))
+#define AMP_OUT_MUTE 0xb080
+#define AMP_OUT_UNMUTE 0xb000
+#define AMP_OUT_ZERO 0xb000
+/* pinctl values */
+#define PIN_IN (AC_PINCTL_IN_EN)
+#define PIN_VREFHIZ (AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ)
+#define PIN_VREF50 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_50)
+#define PIN_VREFGRD (AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD)
+#define PIN_VREF80 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_80)
+#define PIN_VREF100 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_100)
+#define PIN_OUT (AC_PINCTL_OUT_EN)
+#define PIN_HP (AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN)
+#define PIN_HP_AMP (AC_PINCTL_HP_EN)
+
+unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin);
+unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec,
+ hda_nid_t pin, unsigned int val);
+int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val, bool cached);
+
+/**
+ * _snd_hda_set_pin_ctl - Set a pin-control value safely
+ * @codec: the codec instance
+ * @pin: the pin NID to set the control
+ * @val: the pin-control value (AC_PINCTL_* bits)
+ *
+ * This function sets the pin-control value to the given pin, but
+ * filters out the invalid pin-control bits when the pin has no such
+ * capabilities. For example, when PIN_HP is passed but the pin has no
+ * HP-drive capability, the HP bit is omitted.
+ *
+ * The function doesn't check the input VREF capability bits, though.
+ * Use snd_hda_get_default_vref() to guess the right value.
+ * Also, this function is only for analog pins, not for HDMI pins.
+ */
+static inline int
+snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val)
+{
+ return _snd_hda_set_pin_ctl(codec, pin, val, false);
+}
+
+/**
+ * snd_hda_set_pin_ctl_cache - Set a pin-control value safely
+ * @codec: the codec instance
+ * @pin: the pin NID to set the control
+ * @val: the pin-control value (AC_PINCTL_* bits)
+ *
+ * Just like snd_hda_set_pin_ctl() but write to cache as well.
+ */
+static inline int
+snd_hda_set_pin_ctl_cache(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val)
+{
+ return _snd_hda_set_pin_ctl(codec, pin, val, true);
+}
+
+int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid);
+int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int val);
+
+#define for_each_hda_codec_node(nid, codec) \
+ for ((nid) = (codec)->core.start_nid; (nid) < (codec)->core.end_nid; (nid)++)
+
+/*
+ * get widget capabilities
+ */
+static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
+{
+ if (nid < codec->core.start_nid ||
+ nid >= codec->core.start_nid + codec->core.num_nodes)
+ return 0;
+ return codec->wcaps[nid - codec->core.start_nid];
+}
+
+/* get the widget type from widget capability bits */
+static inline int get_wcaps_type(unsigned int wcaps)
+{
+ if (!wcaps)
+ return -1; /* invalid type */
+ return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+}
+
+static inline unsigned int get_wcaps_channels(u32 wcaps)
+{
+ unsigned int chans;
+
+ chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
+ chans = ((chans << 1) | 1) + 1;
+
+ return chans;
+}
+
+static inline void snd_hda_override_wcaps(struct hda_codec *codec,
+ hda_nid_t nid, u32 val)
+{
+ if (nid >= codec->core.start_nid &&
+ nid < codec->core.start_nid + codec->core.num_nodes)
+ codec->wcaps[nid - codec->core.start_nid] = val;
+}
+
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction);
+int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int caps);
+/**
+ * snd_hda_query_pin_caps - Query PIN capabilities
+ * @codec: the HD-auio codec
+ * @nid: the NID to query
+ *
+ * Query PIN capabilities for the given widget.
+ * Returns the obtained capability bits.
+ *
+ * When cap bits have been already read, this doesn't read again but
+ * returns the cached value.
+ */
+static inline u32
+snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+
+}
+
+/**
+ * snd_hda_override_pin_caps - Override the pin capabilities
+ * @codec: the CODEC
+ * @nid: the NID to override
+ * @caps: the capability bits to set
+ *
+ * Override the cached PIN capabilitiy bits value by the given one.
+ *
+ * Returns zero if successful or a negative error code.
+ */
+static inline int
+snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int caps)
+{
+ return snd_hdac_override_parm(&codec->core, nid, AC_PAR_PIN_CAP, caps);
+}
+
+bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int bits);
+
+#define nid_has_mute(codec, nid, dir) \
+ snd_hda_check_amp_caps(codec, nid, dir, (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE))
+#define nid_has_volume(codec, nid, dir) \
+ snd_hda_check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS)
+
+
+/* flags for hda_nid_item */
+#define HDA_NID_ITEM_AMP (1<<0)
+
+struct hda_nid_item {
+ struct snd_kcontrol *kctl;
+ unsigned int index;
+ hda_nid_t nid;
+ unsigned short flags;
+};
+
+int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
+ struct snd_kcontrol *kctl);
+int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
+ unsigned int index, hda_nid_t nid);
+void snd_hda_ctls_clear(struct hda_codec *codec);
+
+/*
+ * hwdep interface
+ */
+#ifdef CONFIG_SND_HDA_HWDEP
+int snd_hda_create_hwdep(struct hda_codec *codec);
+#else
+static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; }
+#endif
+
+void snd_hda_sysfs_init(struct hda_codec *codec);
+void snd_hda_sysfs_clear(struct hda_codec *codec);
+
+extern const struct attribute_group *snd_hda_dev_attr_groups[];
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key);
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key);
+int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp);
+#else
+static inline
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
+{
+ return NULL;
+}
+
+static inline
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
+{
+ return -ENOENT;
+}
+
+static inline
+int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
+{
+ return -ENOENT;
+}
+#endif
+
+/*
+ * power-management
+ */
+
+void snd_hda_schedule_power_save(struct hda_codec *codec);
+
+struct hda_amp_list {
+ hda_nid_t nid;
+ unsigned char dir;
+ unsigned char idx;
+};
+
+struct hda_loopback_check {
+ const struct hda_amp_list *amplist;
+ int power_on;
+};
+
+int snd_hda_check_amp_list_power(struct hda_codec *codec,
+ struct hda_loopback_check *check,
+ hda_nid_t nid);
+
+/* check whether the actual power state matches with the target state */
+static inline bool
+snd_hda_check_power_state(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int target_state)
+{
+ return snd_hdac_check_power_state(&codec->core, nid, target_state);
+}
+
+static inline unsigned int snd_hda_sync_power_state(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int target_state)
+{
+ return snd_hdac_sync_power_state(&codec->core, nid, target_state);
+}
+unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state);
+
+/*
+ * AMP control callbacks
+ */
+/* retrieve parameters from private_value */
+#define get_amp_nid_(pv) ((pv) & 0xffff)
+#define get_amp_nid(kc) get_amp_nid_((kc)->private_value)
+#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
+#define get_amp_direction_(pv) (((pv) >> 18) & 0x1)
+#define get_amp_direction(kc) get_amp_direction_((kc)->private_value)
+#define get_amp_index_(pv) (((pv) >> 19) & 0xf)
+#define get_amp_index(kc) get_amp_index_((kc)->private_value)
+#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f)
+#define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1)
+
+/*
+ * enum control helper
+ */
+int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo,
+ int num_items, const char * const *texts);
+#define snd_hda_enum_bool_helper_info(kcontrol, uinfo) \
+ snd_hda_enum_helper_info(kcontrol, uinfo, 0, NULL)
+
+/*
+ * CEA Short Audio Descriptor data
+ */
+struct cea_sad {
+ int channels;
+ int format; /* (format == 0) indicates invalid SAD */
+ int rates;
+ int sample_bits; /* for LPCM */
+ int max_bitrate; /* for AC3...ATRAC */
+ int profile; /* for WMAPRO */
+};
+
+#define ELD_FIXED_BYTES 20
+#define ELD_MAX_SIZE 256
+#define ELD_MAX_MNL 16
+#define ELD_MAX_SAD 16
+
+/*
+ * ELD: EDID Like Data
+ */
+struct parsed_hdmi_eld {
+ /*
+ * all fields will be cleared before updating ELD
+ */
+ int baseline_len;
+ int eld_ver;
+ int cea_edid_ver;
+ char monitor_name[ELD_MAX_MNL + 1];
+ int manufacture_id;
+ int product_id;
+ u64 port_id;
+ int support_hdcp;
+ int support_ai;
+ int conn_type;
+ int aud_synch_delay;
+ int spk_alloc;
+ int sad_count;
+ struct cea_sad sad[ELD_MAX_SAD];
+};
+
+struct hdmi_eld {
+ bool monitor_present;
+ bool eld_valid;
+ int eld_size;
+ char eld_buffer[ELD_MAX_SIZE];
+ struct parsed_hdmi_eld info;
+};
+
+int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid);
+int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size);
+int snd_hdmi_parse_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e,
+ const unsigned char *buf, int size);
+void snd_hdmi_show_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e);
+void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e,
+ struct hda_pcm_stream *hinfo);
+
+int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size,
+ bool rev3_or_later);
+
+#ifdef CONFIG_SND_PROC_FS
+void snd_hdmi_print_eld_info(struct hdmi_eld *eld,
+ struct snd_info_buffer *buffer);
+void snd_hdmi_write_eld_info(struct hdmi_eld *eld,
+ struct snd_info_buffer *buffer);
+#endif
+
+#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80
+void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
+
+/*
+ */
+#define codec_err(codec, fmt, args...) \
+ dev_err(hda_codec_dev(codec), fmt, ##args)
+#define codec_warn(codec, fmt, args...) \
+ dev_warn(hda_codec_dev(codec), fmt, ##args)
+#define codec_info(codec, fmt, args...) \
+ dev_info(hda_codec_dev(codec), fmt, ##args)
+#define codec_dbg(codec, fmt, args...) \
+ dev_dbg(hda_codec_dev(codec), fmt, ##args)
+
+#endif /* __SOUND_HDA_LOCAL_H */
diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c
new file mode 100644
index 000000000..0631f31ef
--- /dev/null
+++ b/sound/pci/hda/hda_proc.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Generic proc interface
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <linux/module.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+
+static int dump_coef = -1;
+module_param(dump_coef, int, 0644);
+MODULE_PARM_DESC(dump_coef, "Dump processing coefficients in codec proc file (-1=auto, 0=disable, 1=enable)");
+
+/* always use noncached version */
+#define param_read(codec, nid, parm) \
+ snd_hdac_read_parm_uncached(&(codec)->core, nid, parm)
+
+static const char *get_wid_type_name(unsigned int wid_value)
+{
+ static const char * const names[16] = {
+ [AC_WID_AUD_OUT] = "Audio Output",
+ [AC_WID_AUD_IN] = "Audio Input",
+ [AC_WID_AUD_MIX] = "Audio Mixer",
+ [AC_WID_AUD_SEL] = "Audio Selector",
+ [AC_WID_PIN] = "Pin Complex",
+ [AC_WID_POWER] = "Power Widget",
+ [AC_WID_VOL_KNB] = "Volume Knob Widget",
+ [AC_WID_BEEP] = "Beep Generator Widget",
+ [AC_WID_VENDOR] = "Vendor Defined Widget",
+ };
+ if (wid_value == -1)
+ return "UNKNOWN Widget";
+ wid_value &= 0xf;
+ if (names[wid_value])
+ return names[wid_value];
+ else
+ return "UNKNOWN Widget";
+}
+
+static void print_nid_array(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ struct snd_array *array)
+{
+ int i;
+ struct hda_nid_item *items = array->list, *item;
+ struct snd_kcontrol *kctl;
+ for (i = 0; i < array->used; i++) {
+ item = &items[i];
+ if (item->nid == nid) {
+ kctl = item->kctl;
+ snd_iprintf(buffer,
+ " Control: name=\"%s\", index=%i, device=%i\n",
+ kctl->id.name, kctl->id.index + item->index,
+ kctl->id.device);
+ if (item->flags & HDA_NID_ITEM_AMP)
+ snd_iprintf(buffer,
+ " ControlAmp: chs=%lu, dir=%s, "
+ "idx=%lu, ofs=%lu\n",
+ get_amp_channels(kctl),
+ get_amp_direction(kctl) ? "Out" : "In",
+ get_amp_index(kctl),
+ get_amp_offset(kctl));
+ }
+ }
+}
+
+static void print_nid_pcms(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ int type;
+ struct hda_pcm *cpcm;
+
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ for (type = 0; type < 2; type++) {
+ if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL)
+ continue;
+ snd_iprintf(buffer, " Device: name=\"%s\", "
+ "type=\"%s\", device=%i\n",
+ cpcm->name,
+ snd_hda_pcm_type_name[cpcm->pcm_type],
+ cpcm->pcm->device);
+ }
+ }
+}
+
+static void print_amp_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid, int dir)
+{
+ unsigned int caps;
+ caps = param_read(codec, nid, dir == HDA_OUTPUT ?
+ AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+ if (caps == -1 || caps == 0) {
+ snd_iprintf(buffer, "N/A\n");
+ return;
+ }
+ snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, "
+ "mute=%x\n",
+ caps & AC_AMPCAP_OFFSET,
+ (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
+ (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
+ (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
+}
+
+/* is this a stereo widget or a stereo-to-mono mix? */
+static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int wcaps, int indices)
+{
+ hda_nid_t conn;
+
+ if (wcaps & AC_WCAP_STEREO)
+ return true;
+ /* check for a stereo-to-mono mix; it must be:
+ * only a single connection, only for input, and only a mixer widget
+ */
+ if (indices != 1 || dir != HDA_INPUT ||
+ get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
+ return false;
+
+ if (snd_hda_get_raw_connections(codec, nid, &conn, 1) < 0)
+ return false;
+ /* the connection source is a stereo? */
+ wcaps = snd_hda_param_read(codec, conn, AC_PAR_AUDIO_WIDGET_CAP);
+ return !!(wcaps & AC_WCAP_STEREO);
+}
+
+static void print_amp_vals(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int wcaps, int indices)
+{
+ unsigned int val;
+ bool stereo;
+ int i;
+
+ stereo = is_stereo_amps(codec, nid, dir, wcaps, indices);
+
+ dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
+ for (i = 0; i < indices; i++) {
+ snd_iprintf(buffer, " [");
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_AMP_GAIN_MUTE,
+ AC_AMP_GET_LEFT | dir | i);
+ snd_iprintf(buffer, "0x%02x", val);
+ if (stereo) {
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_AMP_GAIN_MUTE,
+ AC_AMP_GET_RIGHT | dir | i);
+ snd_iprintf(buffer, " 0x%02x", val);
+ }
+ snd_iprintf(buffer, "]");
+ }
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm)
+{
+ static const unsigned int rates[] = {
+ 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 384000
+ };
+ int i;
+
+ pcm &= AC_SUPPCM_RATES;
+ snd_iprintf(buffer, " rates [0x%x]:", pcm);
+ for (i = 0; i < ARRAY_SIZE(rates); i++)
+ if (pcm & (1 << i))
+ snd_iprintf(buffer, " %d", rates[i]);
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm)
+{
+ char buf[SND_PRINT_BITS_ADVISED_BUFSIZE];
+
+ snd_iprintf(buffer, " bits [0x%x]:", (pcm >> 16) & 0xff);
+ snd_print_pcm_bits(pcm, buf, sizeof(buf));
+ snd_iprintf(buffer, "%s\n", buf);
+}
+
+static void print_pcm_formats(struct snd_info_buffer *buffer,
+ unsigned int streams)
+{
+ snd_iprintf(buffer, " formats [0x%x]:", streams & 0xf);
+ if (streams & AC_SUPFMT_PCM)
+ snd_iprintf(buffer, " PCM");
+ if (streams & AC_SUPFMT_FLOAT32)
+ snd_iprintf(buffer, " FLOAT");
+ if (streams & AC_SUPFMT_AC3)
+ snd_iprintf(buffer, " AC3");
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int pcm = param_read(codec, nid, AC_PAR_PCM);
+ unsigned int stream = param_read(codec, nid, AC_PAR_STREAM);
+ if (pcm == -1 || stream == -1) {
+ snd_iprintf(buffer, "N/A\n");
+ return;
+ }
+ print_pcm_rates(buffer, pcm);
+ print_pcm_bits(buffer, pcm);
+ print_pcm_formats(buffer, stream);
+}
+
+static const char *get_jack_connection(u32 cfg)
+{
+ static const char * const names[16] = {
+ "Unknown", "1/8", "1/4", "ATAPI",
+ "RCA", "Optical","Digital", "Analog",
+ "DIN", "XLR", "RJ11", "Comb",
+ NULL, NULL, NULL, "Other"
+ };
+ cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
+ if (names[cfg])
+ return names[cfg];
+ else
+ return "UNKNOWN";
+}
+
+static const char *get_jack_color(u32 cfg)
+{
+ static const char * const names[16] = {
+ "Unknown", "Black", "Grey", "Blue",
+ "Green", "Red", "Orange", "Yellow",
+ "Purple", "Pink", NULL, NULL,
+ NULL, NULL, "White", "Other",
+ };
+ cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
+ if (names[cfg])
+ return names[cfg];
+ else
+ return "UNKNOWN";
+}
+
+/*
+ * Parse the pin default config value and returns the string of the
+ * jack location, e.g. "Rear", "Front", etc.
+ */
+static const char *get_jack_location(u32 cfg)
+{
+ static const char * const bases[7] = {
+ "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
+ };
+ static const unsigned char specials_idx[] = {
+ 0x07, 0x08,
+ 0x17, 0x18, 0x19,
+ 0x37, 0x38
+ };
+ static const char * const specials[] = {
+ "Rear Panel", "Drive Bar",
+ "Riser", "HDMI", "ATAPI",
+ "Mobile-In", "Mobile-Out"
+ };
+ int i;
+
+ cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+ if ((cfg & 0x0f) < 7)
+ return bases[cfg & 0x0f];
+ for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
+ if (cfg == specials_idx[i])
+ return specials[i];
+ }
+ return "UNKNOWN";
+}
+
+/*
+ * Parse the pin default config value and returns the string of the
+ * jack connectivity, i.e. external or internal connection.
+ */
+static const char *get_jack_connectivity(u32 cfg)
+{
+ static const char * const jack_locations[4] = {
+ "Ext", "Int", "Sep", "Oth"
+ };
+
+ return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3];
+}
+
+/*
+ * Parse the pin default config value and returns the string of the
+ * jack type, i.e. the purpose of the jack, such as Line-Out or CD.
+ */
+static const char *get_jack_type(u32 cfg)
+{
+ static const char * const jack_types[16] = {
+ "Line Out", "Speaker", "HP Out", "CD",
+ "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
+ "Line In", "Aux", "Mic", "Telephony",
+ "SPDIF In", "Digital In", "Reserved", "Other"
+ };
+
+ return jack_types[(cfg & AC_DEFCFG_DEVICE)
+ >> AC_DEFCFG_DEVICE_SHIFT];
+}
+
+static void print_pin_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int *supports_vref)
+{
+ static const char * const jack_conns[4] = {
+ "Jack", "N/A", "Fixed", "Both"
+ };
+ unsigned int caps, val;
+
+ caps = param_read(codec, nid, AC_PAR_PIN_CAP);
+ snd_iprintf(buffer, " Pincap 0x%08x:", caps);
+ if (caps & AC_PINCAP_IN)
+ snd_iprintf(buffer, " IN");
+ if (caps & AC_PINCAP_OUT)
+ snd_iprintf(buffer, " OUT");
+ if (caps & AC_PINCAP_HP_DRV)
+ snd_iprintf(buffer, " HP");
+ if (caps & AC_PINCAP_EAPD)
+ snd_iprintf(buffer, " EAPD");
+ if (caps & AC_PINCAP_PRES_DETECT)
+ snd_iprintf(buffer, " Detect");
+ if (caps & AC_PINCAP_BALANCE)
+ snd_iprintf(buffer, " Balanced");
+ if (caps & AC_PINCAP_HDMI) {
+ /* Realtek uses this bit as a different meaning */
+ if ((codec->core.vendor_id >> 16) == 0x10ec)
+ snd_iprintf(buffer, " R/L");
+ else {
+ if (caps & AC_PINCAP_HBR)
+ snd_iprintf(buffer, " HBR");
+ snd_iprintf(buffer, " HDMI");
+ }
+ }
+ if (caps & AC_PINCAP_DP)
+ snd_iprintf(buffer, " DP");
+ if (caps & AC_PINCAP_TRIG_REQ)
+ snd_iprintf(buffer, " Trigger");
+ if (caps & AC_PINCAP_IMP_SENSE)
+ snd_iprintf(buffer, " ImpSense");
+ snd_iprintf(buffer, "\n");
+ if (caps & AC_PINCAP_VREF) {
+ unsigned int vref =
+ (caps & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ snd_iprintf(buffer, " Vref caps:");
+ if (vref & AC_PINCAP_VREF_HIZ)
+ snd_iprintf(buffer, " HIZ");
+ if (vref & AC_PINCAP_VREF_50)
+ snd_iprintf(buffer, " 50");
+ if (vref & AC_PINCAP_VREF_GRD)
+ snd_iprintf(buffer, " GRD");
+ if (vref & AC_PINCAP_VREF_80)
+ snd_iprintf(buffer, " 80");
+ if (vref & AC_PINCAP_VREF_100)
+ snd_iprintf(buffer, " 100");
+ snd_iprintf(buffer, "\n");
+ *supports_vref = 1;
+ } else
+ *supports_vref = 0;
+ if (caps & AC_PINCAP_EAPD) {
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_EAPD_BTLENABLE, 0);
+ snd_iprintf(buffer, " EAPD 0x%x:", val);
+ if (val & AC_EAPDBTL_BALANCED)
+ snd_iprintf(buffer, " BALANCED");
+ if (val & AC_EAPDBTL_EAPD)
+ snd_iprintf(buffer, " EAPD");
+ if (val & AC_EAPDBTL_LR_SWAP)
+ snd_iprintf(buffer, " R/L");
+ snd_iprintf(buffer, "\n");
+ }
+ caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+ snd_iprintf(buffer, " Pin Default 0x%08x: [%s] %s at %s %s\n", caps,
+ jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT],
+ get_jack_type(caps),
+ get_jack_connectivity(caps),
+ get_jack_location(caps));
+ snd_iprintf(buffer, " Conn = %s, Color = %s\n",
+ get_jack_connection(caps),
+ get_jack_color(caps));
+ /* Default association and sequence values refer to default grouping
+ * of pin complexes and their sequence within the group. This is used
+ * for priority and resource allocation.
+ */
+ snd_iprintf(buffer, " DefAssociation = 0x%x, Sequence = 0x%x\n",
+ (caps & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT,
+ caps & AC_DEFCFG_SEQUENCE);
+ if (((caps & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) &
+ AC_DEFCFG_MISC_NO_PRESENCE) {
+ /* Miscellaneous bit indicates external hardware does not
+ * support presence detection even if the pin complex
+ * indicates it is supported.
+ */
+ snd_iprintf(buffer, " Misc = NO_PRESENCE\n");
+ }
+}
+
+static void print_pin_ctls(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int supports_vref)
+{
+ unsigned int pinctls;
+
+ pinctls = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls);
+ if (pinctls & AC_PINCTL_IN_EN)
+ snd_iprintf(buffer, " IN");
+ if (pinctls & AC_PINCTL_OUT_EN)
+ snd_iprintf(buffer, " OUT");
+ if (pinctls & AC_PINCTL_HP_EN)
+ snd_iprintf(buffer, " HP");
+ if (supports_vref) {
+ int vref = pinctls & AC_PINCTL_VREFEN;
+ switch (vref) {
+ case AC_PINCTL_VREF_HIZ:
+ snd_iprintf(buffer, " VREF_HIZ");
+ break;
+ case AC_PINCTL_VREF_50:
+ snd_iprintf(buffer, " VREF_50");
+ break;
+ case AC_PINCTL_VREF_GRD:
+ snd_iprintf(buffer, " VREF_GRD");
+ break;
+ case AC_PINCTL_VREF_80:
+ snd_iprintf(buffer, " VREF_80");
+ break;
+ case AC_PINCTL_VREF_100:
+ snd_iprintf(buffer, " VREF_100");
+ break;
+ }
+ }
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_vol_knob(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int cap = param_read(codec, nid, AC_PAR_VOL_KNB_CAP);
+ snd_iprintf(buffer, " Volume-Knob: delta=%d, steps=%d, ",
+ (cap >> 7) & 1, cap & 0x7f);
+ cap = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
+ snd_iprintf(buffer, "direct=%d, val=%d\n",
+ (cap >> 7) & 1, cap & 0x7f);
+}
+
+static void print_audio_io(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ unsigned int wid_type)
+{
+ int conv = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+ snd_iprintf(buffer,
+ " Converter: stream=%d, channel=%d\n",
+ (conv & AC_CONV_STREAM) >> AC_CONV_STREAM_SHIFT,
+ conv & AC_CONV_CHANNEL);
+
+ if (wid_type == AC_WID_AUD_IN && (conv & AC_CONV_CHANNEL) == 0) {
+ int sdi = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_SDI_SELECT, 0);
+ snd_iprintf(buffer, " SDI-Select: %d\n",
+ sdi & AC_SDI_SELECT);
+ }
+}
+
+static void print_digital_conv(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int digi1 = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DIGI_CONVERT_1, 0);
+ unsigned char digi2 = digi1 >> 8;
+ unsigned char digi3 = digi1 >> 16;
+
+ snd_iprintf(buffer, " Digital:");
+ if (digi1 & AC_DIG1_ENABLE)
+ snd_iprintf(buffer, " Enabled");
+ if (digi1 & AC_DIG1_V)
+ snd_iprintf(buffer, " Validity");
+ if (digi1 & AC_DIG1_VCFG)
+ snd_iprintf(buffer, " ValidityCfg");
+ if (digi1 & AC_DIG1_EMPHASIS)
+ snd_iprintf(buffer, " Preemphasis");
+ if (digi1 & AC_DIG1_COPYRIGHT)
+ snd_iprintf(buffer, " Non-Copyright");
+ if (digi1 & AC_DIG1_NONAUDIO)
+ snd_iprintf(buffer, " Non-Audio");
+ if (digi1 & AC_DIG1_PROFESSIONAL)
+ snd_iprintf(buffer, " Pro");
+ if (digi1 & AC_DIG1_LEVEL)
+ snd_iprintf(buffer, " GenLevel");
+ if (digi3 & AC_DIG3_KAE)
+ snd_iprintf(buffer, " KAE");
+ snd_iprintf(buffer, "\n");
+ snd_iprintf(buffer, " Digital category: 0x%x\n",
+ digi2 & AC_DIG2_CC);
+ snd_iprintf(buffer, " IEC Coding Type: 0x%x\n",
+ digi3 & AC_DIG3_ICT);
+}
+
+static const char *get_pwr_state(u32 state)
+{
+ static const char * const buf[] = {
+ "D0", "D1", "D2", "D3", "D3cold"
+ };
+ if (state < ARRAY_SIZE(buf))
+ return buf[state];
+ return "UNKNOWN";
+}
+
+static void print_power_state(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ static const char * const names[] = {
+ [ilog2(AC_PWRST_D0SUP)] = "D0",
+ [ilog2(AC_PWRST_D1SUP)] = "D1",
+ [ilog2(AC_PWRST_D2SUP)] = "D2",
+ [ilog2(AC_PWRST_D3SUP)] = "D3",
+ [ilog2(AC_PWRST_D3COLDSUP)] = "D3cold",
+ [ilog2(AC_PWRST_S3D3COLDSUP)] = "S3D3cold",
+ [ilog2(AC_PWRST_CLKSTOP)] = "CLKSTOP",
+ [ilog2(AC_PWRST_EPSS)] = "EPSS",
+ };
+
+ int sup = param_read(codec, nid, AC_PAR_POWER_STATE);
+ int pwr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_POWER_STATE, 0);
+ if (sup != -1) {
+ int i;
+
+ snd_iprintf(buffer, " Power states: ");
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ if (sup & (1U << i))
+ snd_iprintf(buffer, " %s", names[i]);
+ }
+ snd_iprintf(buffer, "\n");
+ }
+
+ snd_iprintf(buffer, " Power: setting=%s, actual=%s",
+ get_pwr_state(pwr & AC_PWRST_SETTING),
+ get_pwr_state((pwr & AC_PWRST_ACTUAL) >>
+ AC_PWRST_ACTUAL_SHIFT));
+ if (pwr & AC_PWRST_ERROR)
+ snd_iprintf(buffer, ", Error");
+ if (pwr & AC_PWRST_CLK_STOP_OK)
+ snd_iprintf(buffer, ", Clock-stop-OK");
+ if (pwr & AC_PWRST_SETTING_RESET)
+ snd_iprintf(buffer, ", Setting-reset");
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_unsol_cap(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ int unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_UNSOLICITED_RESPONSE, 0);
+ snd_iprintf(buffer,
+ " Unsolicited: tag=%02x, enabled=%d\n",
+ unsol & AC_UNSOL_TAG,
+ (unsol & AC_UNSOL_ENABLED) ? 1 : 0);
+}
+
+static inline bool can_dump_coef(struct hda_codec *codec)
+{
+ switch (dump_coef) {
+ case 0: return false;
+ case 1: return true;
+ default: return codec->dump_coef;
+ }
+}
+
+static void print_proc_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int i, ncoeff, oldindex;
+ unsigned int proc_caps = param_read(codec, nid, AC_PAR_PROC_CAP);
+ ncoeff = (proc_caps & AC_PCAP_NUM_COEF) >> AC_PCAP_NUM_COEF_SHIFT;
+ snd_iprintf(buffer, " Processing caps: benign=%d, ncoeff=%d\n",
+ proc_caps & AC_PCAP_BENIGN, ncoeff);
+
+ if (!can_dump_coef(codec))
+ return;
+
+ /* Note: This is racy - another process could run in parallel and change
+ the coef index too. */
+ oldindex = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_COEF_INDEX, 0);
+ for (i = 0; i < ncoeff; i++) {
+ unsigned int val;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, i);
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF,
+ 0);
+ snd_iprintf(buffer, " Coeff 0x%02x: 0x%04x\n", i, val);
+ }
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, oldindex);
+}
+
+static void print_conn_list(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ unsigned int wid_type, hda_nid_t *conn,
+ int conn_len)
+{
+ int c, curr = -1;
+ const hda_nid_t *list;
+ int cache_len;
+
+ if (conn_len > 1 &&
+ wid_type != AC_WID_AUD_MIX &&
+ wid_type != AC_WID_VOL_KNB &&
+ wid_type != AC_WID_POWER)
+ curr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_CONNECT_SEL, 0);
+ snd_iprintf(buffer, " Connection: %d\n", conn_len);
+ if (conn_len > 0) {
+ snd_iprintf(buffer, " ");
+ for (c = 0; c < conn_len; c++) {
+ snd_iprintf(buffer, " 0x%02x", conn[c]);
+ if (c == curr)
+ snd_iprintf(buffer, "*");
+ }
+ snd_iprintf(buffer, "\n");
+ }
+
+ /* Get Cache connections info */
+ cache_len = snd_hda_get_conn_list(codec, nid, &list);
+ if (cache_len >= 0 && (cache_len != conn_len ||
+ memcmp(list, conn, conn_len) != 0)) {
+ snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len);
+ if (cache_len > 0) {
+ snd_iprintf(buffer, " ");
+ for (c = 0; c < cache_len; c++)
+ snd_iprintf(buffer, " 0x%02x", list[c]);
+ snd_iprintf(buffer, "\n");
+ }
+ }
+}
+
+static void print_gpio(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int gpio =
+ param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP);
+ unsigned int enable, direction, wake, unsol, sticky, data;
+ int i, max;
+ snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, "
+ "unsolicited=%d, wake=%d\n",
+ gpio & AC_GPIO_IO_COUNT,
+ (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT,
+ (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT,
+ (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0,
+ (gpio & AC_GPIO_WAKE) ? 1 : 0);
+ max = gpio & AC_GPIO_IO_COUNT;
+ if (!max || max > 8)
+ return;
+ enable = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_MASK, 0);
+ direction = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DIRECTION, 0);
+ wake = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_WAKE_MASK, 0);
+ unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0);
+ sticky = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_STICKY_MASK, 0);
+ data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+ for (i = 0; i < max; ++i)
+ snd_iprintf(buffer,
+ " IO[%d]: enable=%d, dir=%d, wake=%d, "
+ "sticky=%d, data=%d, unsol=%d\n", i,
+ (enable & (1<<i)) ? 1 : 0,
+ (direction & (1<<i)) ? 1 : 0,
+ (wake & (1<<i)) ? 1 : 0,
+ (sticky & (1<<i)) ? 1 : 0,
+ (data & (1<<i)) ? 1 : 0,
+ (unsol & (1<<i)) ? 1 : 0);
+ /* FIXME: add GPO and GPI pin information */
+ print_nid_array(buffer, codec, nid, &codec->mixers);
+ print_nid_array(buffer, codec, nid, &codec->nids);
+}
+
+static void print_device_list(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ int i, curr = -1;
+ u8 dev_list[AC_MAX_DEV_LIST_LEN];
+ int devlist_len;
+
+ devlist_len = snd_hda_get_devices(codec, nid, dev_list,
+ AC_MAX_DEV_LIST_LEN);
+ snd_iprintf(buffer, " Devices: %d\n", devlist_len);
+ if (devlist_len <= 0)
+ return;
+
+ curr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DEVICE_SEL, 0);
+
+ for (i = 0; i < devlist_len; i++) {
+ if (i == curr)
+ snd_iprintf(buffer, " *");
+ else
+ snd_iprintf(buffer, " ");
+
+ snd_iprintf(buffer,
+ "Dev %02d: PD = %d, ELDV = %d, IA = %d\n", i,
+ !!(dev_list[i] & AC_DE_PD),
+ !!(dev_list[i] & AC_DE_ELDV),
+ !!(dev_list[i] & AC_DE_IA));
+ }
+}
+
+static void print_codec_core_info(struct hdac_device *codec,
+ struct snd_info_buffer *buffer)
+{
+ snd_iprintf(buffer, "Codec: ");
+ if (codec->vendor_name && codec->chip_name)
+ snd_iprintf(buffer, "%s %s\n",
+ codec->vendor_name, codec->chip_name);
+ else
+ snd_iprintf(buffer, "Not Set\n");
+ snd_iprintf(buffer, "Address: %d\n", codec->addr);
+ if (codec->afg)
+ snd_iprintf(buffer, "AFG Function Id: 0x%x (unsol %u)\n",
+ codec->afg_function_id, codec->afg_unsol);
+ if (codec->mfg)
+ snd_iprintf(buffer, "MFG Function Id: 0x%x (unsol %u)\n",
+ codec->mfg_function_id, codec->mfg_unsol);
+ snd_iprintf(buffer, "Vendor Id: 0x%08x\n", codec->vendor_id);
+ snd_iprintf(buffer, "Subsystem Id: 0x%08x\n", codec->subsystem_id);
+ snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
+
+ if (codec->mfg)
+ snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg);
+ else
+ snd_iprintf(buffer, "No Modem Function Group found\n");
+}
+
+static void print_codec_info(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct hda_codec *codec = entry->private_data;
+ hda_nid_t nid, fg;
+ int i, nodes;
+
+ print_codec_core_info(&codec->core, buffer);
+ fg = codec->core.afg;
+ if (!fg)
+ return;
+ snd_hda_power_up(codec);
+ snd_iprintf(buffer, "Default PCM:\n");
+ print_pcm_caps(buffer, codec, fg);
+ snd_iprintf(buffer, "Default Amp-In caps: ");
+ print_amp_caps(buffer, codec, fg, HDA_INPUT);
+ snd_iprintf(buffer, "Default Amp-Out caps: ");
+ print_amp_caps(buffer, codec, fg, HDA_OUTPUT);
+ snd_iprintf(buffer, "State of AFG node 0x%02x:\n", fg);
+ print_power_state(buffer, codec, fg);
+
+ nodes = snd_hda_get_sub_nodes(codec, fg, &nid);
+ if (! nid || nodes < 0) {
+ snd_iprintf(buffer, "Invalid AFG subtree\n");
+ snd_hda_power_down(codec);
+ return;
+ }
+
+ print_gpio(buffer, codec, fg);
+ if (codec->proc_widget_hook)
+ codec->proc_widget_hook(buffer, codec, fg);
+
+ for (i = 0; i < nodes; i++, nid++) {
+ unsigned int wid_caps =
+ param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+ unsigned int wid_type = get_wcaps_type(wid_caps);
+ hda_nid_t *conn = NULL;
+ int conn_len = 0;
+
+ snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
+ get_wid_type_name(wid_type), wid_caps);
+ if (wid_caps & AC_WCAP_STEREO) {
+ unsigned int chans = get_wcaps_channels(wid_caps);
+ if (chans == 2)
+ snd_iprintf(buffer, " Stereo");
+ else
+ snd_iprintf(buffer, " %d-Channels", chans);
+ } else
+ snd_iprintf(buffer, " Mono");
+ if (wid_caps & AC_WCAP_DIGITAL)
+ snd_iprintf(buffer, " Digital");
+ if (wid_caps & AC_WCAP_IN_AMP)
+ snd_iprintf(buffer, " Amp-In");
+ if (wid_caps & AC_WCAP_OUT_AMP)
+ snd_iprintf(buffer, " Amp-Out");
+ if (wid_caps & AC_WCAP_STRIPE)
+ snd_iprintf(buffer, " Stripe");
+ if (wid_caps & AC_WCAP_LR_SWAP)
+ snd_iprintf(buffer, " R/L");
+ if (wid_caps & AC_WCAP_CP_CAPS)
+ snd_iprintf(buffer, " CP");
+ snd_iprintf(buffer, "\n");
+
+ print_nid_array(buffer, codec, nid, &codec->mixers);
+ print_nid_array(buffer, codec, nid, &codec->nids);
+ print_nid_pcms(buffer, codec, nid);
+
+ /* volume knob is a special widget that always have connection
+ * list
+ */
+ if (wid_type == AC_WID_VOL_KNB)
+ wid_caps |= AC_WCAP_CONN_LIST;
+
+ if (wid_caps & AC_WCAP_CONN_LIST) {
+ conn_len = snd_hda_get_num_raw_conns(codec, nid);
+ if (conn_len > 0) {
+ conn = kmalloc_array(conn_len,
+ sizeof(hda_nid_t),
+ GFP_KERNEL);
+ if (!conn)
+ return;
+ if (snd_hda_get_raw_connections(codec, nid, conn,
+ conn_len) < 0)
+ conn_len = 0;
+ }
+ }
+
+ if (wid_caps & AC_WCAP_IN_AMP) {
+ snd_iprintf(buffer, " Amp-In caps: ");
+ print_amp_caps(buffer, codec, nid, HDA_INPUT);
+ snd_iprintf(buffer, " Amp-In vals: ");
+ if (wid_type == AC_WID_PIN ||
+ (codec->single_adc_amp &&
+ wid_type == AC_WID_AUD_IN))
+ print_amp_vals(buffer, codec, nid, HDA_INPUT,
+ wid_caps, 1);
+ else
+ print_amp_vals(buffer, codec, nid, HDA_INPUT,
+ wid_caps, conn_len);
+ }
+ if (wid_caps & AC_WCAP_OUT_AMP) {
+ snd_iprintf(buffer, " Amp-Out caps: ");
+ print_amp_caps(buffer, codec, nid, HDA_OUTPUT);
+ snd_iprintf(buffer, " Amp-Out vals: ");
+ if (wid_type == AC_WID_PIN &&
+ codec->pin_amp_workaround)
+ print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+ wid_caps, conn_len);
+ else
+ print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+ wid_caps, 1);
+ }
+
+ switch (wid_type) {
+ case AC_WID_PIN: {
+ int supports_vref;
+ print_pin_caps(buffer, codec, nid, &supports_vref);
+ print_pin_ctls(buffer, codec, nid, supports_vref);
+ break;
+ }
+ case AC_WID_VOL_KNB:
+ print_vol_knob(buffer, codec, nid);
+ break;
+ case AC_WID_AUD_OUT:
+ case AC_WID_AUD_IN:
+ print_audio_io(buffer, codec, nid, wid_type);
+ if (wid_caps & AC_WCAP_DIGITAL)
+ print_digital_conv(buffer, codec, nid);
+ if (wid_caps & AC_WCAP_FORMAT_OVRD) {
+ snd_iprintf(buffer, " PCM:\n");
+ print_pcm_caps(buffer, codec, nid);
+ }
+ break;
+ }
+
+ if (wid_caps & AC_WCAP_UNSOL_CAP)
+ print_unsol_cap(buffer, codec, nid);
+
+ if (wid_caps & AC_WCAP_POWER)
+ print_power_state(buffer, codec, nid);
+
+ if (wid_caps & AC_WCAP_DELAY)
+ snd_iprintf(buffer, " Delay: %d samples\n",
+ (wid_caps & AC_WCAP_DELAY) >>
+ AC_WCAP_DELAY_SHIFT);
+
+ if (wid_type == AC_WID_PIN && codec->dp_mst)
+ print_device_list(buffer, codec, nid);
+
+ if (wid_caps & AC_WCAP_CONN_LIST)
+ print_conn_list(buffer, codec, nid, wid_type,
+ conn, conn_len);
+
+ if (wid_caps & AC_WCAP_PROC_WID)
+ print_proc_caps(buffer, codec, nid);
+
+ if (codec->proc_widget_hook)
+ codec->proc_widget_hook(buffer, codec, nid);
+
+ kfree(conn);
+ }
+ snd_hda_power_down(codec);
+}
+
+/*
+ * create a proc read
+ */
+int snd_hda_codec_proc_new(struct hda_codec *codec)
+{
+ char name[32];
+
+ snprintf(name, sizeof(name), "codec#%d", codec->core.addr);
+ return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info);
+}
+
diff --git a/sound/pci/hda/hda_sysfs.c b/sound/pci/hda/hda_sysfs.c
new file mode 100644
index 000000000..d5ffcba79
--- /dev/null
+++ b/sound/pci/hda/hda_sysfs.c
@@ -0,0 +1,799 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sysfs interface for HD-audio codec
+ *
+ * Copyright (c) 2014 Takashi Iwai <tiwai@suse.de>
+ *
+ * split from hda_hwdep.c
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+#include <linux/mutex.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include <sound/hda_hwdep.h>
+#include <sound/minors.h>
+
+/* hint string pair */
+struct hda_hint {
+ const char *key;
+ const char *val; /* contained in the same alloc as key */
+};
+
+#ifdef CONFIG_PM
+static ssize_t power_on_acct_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ snd_hda_update_power_acct(codec);
+ return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct));
+}
+
+static ssize_t power_off_acct_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ snd_hda_update_power_acct(codec);
+ return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct));
+}
+
+static DEVICE_ATTR_RO(power_on_acct);
+static DEVICE_ATTR_RO(power_off_acct);
+#endif /* CONFIG_PM */
+
+#define CODEC_INFO_SHOW(type, field) \
+static ssize_t type##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ return sprintf(buf, "0x%x\n", codec->field); \
+}
+
+#define CODEC_INFO_STR_SHOW(type, field) \
+static ssize_t type##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ return sprintf(buf, "%s\n", \
+ codec->field ? codec->field : ""); \
+}
+
+CODEC_INFO_SHOW(vendor_id, core.vendor_id);
+CODEC_INFO_SHOW(subsystem_id, core.subsystem_id);
+CODEC_INFO_SHOW(revision_id, core.revision_id);
+CODEC_INFO_SHOW(afg, core.afg);
+CODEC_INFO_SHOW(mfg, core.mfg);
+CODEC_INFO_STR_SHOW(vendor_name, core.vendor_name);
+CODEC_INFO_STR_SHOW(chip_name, core.chip_name);
+CODEC_INFO_STR_SHOW(modelname, modelname);
+
+static ssize_t pin_configs_show(struct hda_codec *codec,
+ struct snd_array *list,
+ char *buf)
+{
+ const struct hda_pincfg *pin;
+ int i, len = 0;
+ mutex_lock(&codec->user_mutex);
+ snd_array_for_each(list, i, pin) {
+ len += sprintf(buf + len, "0x%02x 0x%08x\n",
+ pin->nid, pin->cfg);
+ }
+ mutex_unlock(&codec->user_mutex);
+ return len;
+}
+
+static ssize_t init_pin_configs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ return pin_configs_show(codec, &codec->init_pins, buf);
+}
+
+static ssize_t driver_pin_configs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ return pin_configs_show(codec, &codec->driver_pins, buf);
+}
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+
+/*
+ * sysfs interface
+ */
+
+static int clear_codec(struct hda_codec *codec)
+{
+ int err;
+
+ err = snd_hda_codec_reset(codec);
+ if (err < 0) {
+ codec_err(codec, "The codec is being used, can't free.\n");
+ return err;
+ }
+ snd_hda_sysfs_clear(codec);
+ return 0;
+}
+
+static int reconfig_codec(struct hda_codec *codec)
+{
+ int err;
+
+ snd_hda_power_up(codec);
+ codec_info(codec, "hda-codec: reconfiguring\n");
+ err = snd_hda_codec_reset(codec);
+ if (err < 0) {
+ codec_err(codec,
+ "The codec is being used, can't reconfigure.\n");
+ goto error;
+ }
+ err = device_reprobe(hda_codec_dev(codec));
+ if (err < 0)
+ goto error;
+ err = snd_card_register(codec->card);
+ error:
+ snd_hda_power_down(codec);
+ return err;
+}
+
+/*
+ * allocate a string at most len chars, and remove the trailing EOL
+ */
+static char *kstrndup_noeol(const char *src, size_t len)
+{
+ char *s = kstrndup(src, len, GFP_KERNEL);
+ char *p;
+ if (!s)
+ return NULL;
+ p = strchr(s, '\n');
+ if (p)
+ *p = 0;
+ return s;
+}
+
+#define CODEC_INFO_STORE(type, field) \
+static ssize_t type##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ unsigned long val; \
+ int err = kstrtoul(buf, 0, &val); \
+ if (err < 0) \
+ return err; \
+ codec->field = val; \
+ return count; \
+}
+
+#define CODEC_INFO_STR_STORE(type, field) \
+static ssize_t type##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ char *s = kstrndup_noeol(buf, 64); \
+ if (!s) \
+ return -ENOMEM; \
+ kfree(codec->field); \
+ codec->field = s; \
+ return count; \
+}
+
+CODEC_INFO_STORE(vendor_id, core.vendor_id);
+CODEC_INFO_STORE(subsystem_id, core.subsystem_id);
+CODEC_INFO_STORE(revision_id, core.revision_id);
+CODEC_INFO_STR_STORE(vendor_name, core.vendor_name);
+CODEC_INFO_STR_STORE(chip_name, core.chip_name);
+CODEC_INFO_STR_STORE(modelname, modelname);
+
+#define CODEC_ACTION_STORE(type) \
+static ssize_t type##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ int err = 0; \
+ if (*buf) \
+ err = type##_codec(codec); \
+ return err < 0 ? err : count; \
+}
+
+CODEC_ACTION_STORE(reconfig);
+CODEC_ACTION_STORE(clear);
+
+static ssize_t init_verbs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ const struct hda_verb *v;
+ int i, len = 0;
+ mutex_lock(&codec->user_mutex);
+ snd_array_for_each(&codec->init_verbs, i, v) {
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "0x%02x 0x%03x 0x%04x\n",
+ v->nid, v->verb, v->param);
+ }
+ mutex_unlock(&codec->user_mutex);
+ return len;
+}
+
+static int parse_init_verbs(struct hda_codec *codec, const char *buf)
+{
+ struct hda_verb *v;
+ int nid, verb, param;
+
+ if (sscanf(buf, "%i %i %i", &nid, &verb, &param) != 3)
+ return -EINVAL;
+ if (!nid || !verb)
+ return -EINVAL;
+ mutex_lock(&codec->user_mutex);
+ v = snd_array_new(&codec->init_verbs);
+ if (!v) {
+ mutex_unlock(&codec->user_mutex);
+ return -ENOMEM;
+ }
+ v->nid = nid;
+ v->verb = verb;
+ v->param = param;
+ mutex_unlock(&codec->user_mutex);
+ return 0;
+}
+
+static ssize_t init_verbs_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ int err = parse_init_verbs(codec, buf);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static ssize_t hints_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ const struct hda_hint *hint;
+ int i, len = 0;
+ mutex_lock(&codec->user_mutex);
+ snd_array_for_each(&codec->hints, i, hint) {
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "%s = %s\n", hint->key, hint->val);
+ }
+ mutex_unlock(&codec->user_mutex);
+ return len;
+}
+
+static struct hda_hint *get_hint(struct hda_codec *codec, const char *key)
+{
+ struct hda_hint *hint;
+ int i;
+
+ snd_array_for_each(&codec->hints, i, hint) {
+ if (!strcmp(hint->key, key))
+ return hint;
+ }
+ return NULL;
+}
+
+static void remove_trail_spaces(char *str)
+{
+ char *p;
+ if (!*str)
+ return;
+ p = str + strlen(str) - 1;
+ for (; isspace(*p); p--) {
+ *p = 0;
+ if (p == str)
+ return;
+ }
+}
+
+#define MAX_HINTS 1024
+
+static int parse_hints(struct hda_codec *codec, const char *buf)
+{
+ char *key, *val;
+ struct hda_hint *hint;
+ int err = 0;
+
+ buf = skip_spaces(buf);
+ if (!*buf || *buf == '#' || *buf == '\n')
+ return 0;
+ if (*buf == '=')
+ return -EINVAL;
+ key = kstrndup_noeol(buf, 1024);
+ if (!key)
+ return -ENOMEM;
+ /* extract key and val */
+ val = strchr(key, '=');
+ if (!val) {
+ kfree(key);
+ return -EINVAL;
+ }
+ *val++ = 0;
+ val = skip_spaces(val);
+ remove_trail_spaces(key);
+ remove_trail_spaces(val);
+ mutex_lock(&codec->user_mutex);
+ hint = get_hint(codec, key);
+ if (hint) {
+ /* replace */
+ kfree(hint->key);
+ hint->key = key;
+ hint->val = val;
+ goto unlock;
+ }
+ /* allocate a new hint entry */
+ if (codec->hints.used >= MAX_HINTS)
+ hint = NULL;
+ else
+ hint = snd_array_new(&codec->hints);
+ if (hint) {
+ hint->key = key;
+ hint->val = val;
+ } else {
+ err = -ENOMEM;
+ }
+ unlock:
+ mutex_unlock(&codec->user_mutex);
+ if (err)
+ kfree(key);
+ return err;
+}
+
+static ssize_t hints_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ int err = parse_hints(codec, buf);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static ssize_t user_pin_configs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ return pin_configs_show(codec, &codec->user_pins, buf);
+}
+
+#define MAX_PIN_CONFIGS 32
+
+static int parse_user_pin_configs(struct hda_codec *codec, const char *buf)
+{
+ int nid, cfg, err;
+
+ if (sscanf(buf, "%i %i", &nid, &cfg) != 2)
+ return -EINVAL;
+ if (!nid)
+ return -EINVAL;
+ mutex_lock(&codec->user_mutex);
+ err = snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg);
+ mutex_unlock(&codec->user_mutex);
+ return err;
+}
+
+static ssize_t user_pin_configs_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ int err = parse_user_pin_configs(codec, buf);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+/* sysfs attributes exposed only when CONFIG_SND_HDA_RECONFIG=y */
+static DEVICE_ATTR_RW(init_verbs);
+static DEVICE_ATTR_RW(hints);
+static DEVICE_ATTR_RW(user_pin_configs);
+static DEVICE_ATTR_WO(reconfig);
+static DEVICE_ATTR_WO(clear);
+
+/**
+ * snd_hda_get_hint - Look for hint string
+ * @codec: the HDA codec
+ * @key: the hint key string
+ *
+ * Look for a hint key/value pair matching with the given key string
+ * and returns the value string. If nothing found, returns NULL.
+ */
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
+{
+ struct hda_hint *hint = get_hint(codec, key);
+ return hint ? hint->val : NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_hint);
+
+/**
+ * snd_hda_get_bool_hint - Get a boolean hint value
+ * @codec: the HDA codec
+ * @key: the hint key string
+ *
+ * Look for a hint key/value pair matching with the given key string
+ * and returns a boolean value parsed from the value. If no matching
+ * key is found, return a negative value.
+ */
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
+{
+ const char *p;
+ int ret;
+
+ mutex_lock(&codec->user_mutex);
+ p = snd_hda_get_hint(codec, key);
+ if (!p || !*p)
+ ret = -ENOENT;
+ else {
+ switch (toupper(*p)) {
+ case 'T': /* true */
+ case 'Y': /* yes */
+ case '1':
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&codec->user_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_bool_hint);
+
+/**
+ * snd_hda_get_int_hint - Get an integer hint value
+ * @codec: the HDA codec
+ * @key: the hint key string
+ * @valp: pointer to store a value
+ *
+ * Look for a hint key/value pair matching with the given key string
+ * and stores the integer value to @valp. If no matching key is found,
+ * return a negative error code. Otherwise it returns zero.
+ */
+int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
+{
+ const char *p;
+ unsigned long val;
+ int ret;
+
+ mutex_lock(&codec->user_mutex);
+ p = snd_hda_get_hint(codec, key);
+ if (!p)
+ ret = -ENOENT;
+ else if (kstrtoul(p, 0, &val))
+ ret = -EINVAL;
+ else {
+ *valp = val;
+ ret = 0;
+ }
+ mutex_unlock(&codec->user_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_int_hint);
+#endif /* CONFIG_SND_HDA_RECONFIG */
+
+/*
+ * common sysfs attributes
+ */
+#ifdef CONFIG_SND_HDA_RECONFIG
+#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RW(name)
+#else
+#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RO(name)
+#endif
+static RECONFIG_DEVICE_ATTR(vendor_id);
+static RECONFIG_DEVICE_ATTR(subsystem_id);
+static RECONFIG_DEVICE_ATTR(revision_id);
+static DEVICE_ATTR_RO(afg);
+static DEVICE_ATTR_RO(mfg);
+static RECONFIG_DEVICE_ATTR(vendor_name);
+static RECONFIG_DEVICE_ATTR(chip_name);
+static RECONFIG_DEVICE_ATTR(modelname);
+static DEVICE_ATTR_RO(init_pin_configs);
+static DEVICE_ATTR_RO(driver_pin_configs);
+
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+
+/* parser mode */
+enum {
+ LINE_MODE_NONE,
+ LINE_MODE_CODEC,
+ LINE_MODE_MODEL,
+ LINE_MODE_PINCFG,
+ LINE_MODE_VERB,
+ LINE_MODE_HINT,
+ LINE_MODE_VENDOR_ID,
+ LINE_MODE_SUBSYSTEM_ID,
+ LINE_MODE_REVISION_ID,
+ LINE_MODE_CHIP_NAME,
+ NUM_LINE_MODES,
+};
+
+static inline int strmatch(const char *a, const char *b)
+{
+ return strncasecmp(a, b, strlen(b)) == 0;
+}
+
+/* parse the contents after the line "[codec]"
+ * accept only the line with three numbers, and assign the current codec
+ */
+static void parse_codec_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ int vendorid, subid, caddr;
+ struct hda_codec *codec;
+
+ *codecp = NULL;
+ if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) {
+ list_for_each_codec(codec, bus) {
+ if ((vendorid <= 0 || codec->core.vendor_id == vendorid) &&
+ (subid <= 0 || codec->core.subsystem_id == subid) &&
+ codec->core.addr == caddr) {
+ *codecp = codec;
+ break;
+ }
+ }
+ }
+}
+
+/* parse the contents after the other command tags, [pincfg], [verb],
+ * [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model]
+ * just pass to the sysfs helper (only when any codec was specified)
+ */
+static void parse_pincfg_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ parse_user_pin_configs(*codecp, buf);
+}
+
+static void parse_verb_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ parse_init_verbs(*codecp, buf);
+}
+
+static void parse_hint_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ parse_hints(*codecp, buf);
+}
+
+static void parse_model_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ kfree((*codecp)->modelname);
+ (*codecp)->modelname = kstrdup(buf, GFP_KERNEL);
+}
+
+static void parse_chip_name_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ snd_hda_codec_set_name(*codecp, buf);
+}
+
+#define DEFINE_PARSE_ID_MODE(name) \
+static void parse_##name##_mode(char *buf, struct hda_bus *bus, \
+ struct hda_codec **codecp) \
+{ \
+ unsigned long val; \
+ if (!kstrtoul(buf, 0, &val)) \
+ (*codecp)->core.name = val; \
+}
+
+DEFINE_PARSE_ID_MODE(vendor_id);
+DEFINE_PARSE_ID_MODE(subsystem_id);
+DEFINE_PARSE_ID_MODE(revision_id);
+
+
+struct hda_patch_item {
+ const char *tag;
+ const char *alias;
+ void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc);
+};
+
+static const struct hda_patch_item patch_items[NUM_LINE_MODES] = {
+ [LINE_MODE_CODEC] = {
+ .tag = "[codec]",
+ .parser = parse_codec_mode,
+ },
+ [LINE_MODE_MODEL] = {
+ .tag = "[model]",
+ .parser = parse_model_mode,
+ },
+ [LINE_MODE_VERB] = {
+ .tag = "[verb]",
+ .alias = "[init_verbs]",
+ .parser = parse_verb_mode,
+ },
+ [LINE_MODE_PINCFG] = {
+ .tag = "[pincfg]",
+ .alias = "[user_pin_configs]",
+ .parser = parse_pincfg_mode,
+ },
+ [LINE_MODE_HINT] = {
+ .tag = "[hint]",
+ .alias = "[hints]",
+ .parser = parse_hint_mode
+ },
+ [LINE_MODE_VENDOR_ID] = {
+ .tag = "[vendor_id]",
+ .parser = parse_vendor_id_mode,
+ },
+ [LINE_MODE_SUBSYSTEM_ID] = {
+ .tag = "[subsystem_id]",
+ .parser = parse_subsystem_id_mode,
+ },
+ [LINE_MODE_REVISION_ID] = {
+ .tag = "[revision_id]",
+ .parser = parse_revision_id_mode,
+ },
+ [LINE_MODE_CHIP_NAME] = {
+ .tag = "[chip_name]",
+ .parser = parse_chip_name_mode,
+ },
+};
+
+/* check the line starting with '[' -- change the parser mode accodingly */
+static int parse_line_mode(char *buf, struct hda_bus *bus)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(patch_items); i++) {
+ if (!patch_items[i].tag)
+ continue;
+ if (strmatch(buf, patch_items[i].tag))
+ return i;
+ if (patch_items[i].alias && strmatch(buf, patch_items[i].alias))
+ return i;
+ }
+ return LINE_MODE_NONE;
+}
+
+/* copy one line from the buffer in fw, and update the fields in fw
+ * return zero if it reaches to the end of the buffer, or non-zero
+ * if successfully copied a line
+ *
+ * the spaces at the beginning and the end of the line are stripped
+ */
+static int get_line_from_fw(char *buf, int size, size_t *fw_size_p,
+ const void **fw_data_p)
+{
+ int len;
+ size_t fw_size = *fw_size_p;
+ const char *p = *fw_data_p;
+
+ while (isspace(*p) && fw_size) {
+ p++;
+ fw_size--;
+ }
+ if (!fw_size)
+ return 0;
+
+ for (len = 0; len < fw_size; len++) {
+ if (!*p)
+ break;
+ if (*p == '\n') {
+ p++;
+ len++;
+ break;
+ }
+ if (len < size)
+ *buf++ = *p++;
+ }
+ *buf = 0;
+ *fw_size_p = fw_size - len;
+ *fw_data_p = p;
+ remove_trail_spaces(buf);
+ return 1;
+}
+
+/**
+ * snd_hda_load_patch - load a "patch" firmware file and parse it
+ * @bus: HD-audio bus
+ * @fw_size: the firmware byte size
+ * @fw_buf: the firmware data
+ */
+int snd_hda_load_patch(struct hda_bus *bus, size_t fw_size, const void *fw_buf)
+{
+ char buf[128];
+ struct hda_codec *codec;
+ int line_mode;
+
+ line_mode = LINE_MODE_NONE;
+ codec = NULL;
+ while (get_line_from_fw(buf, sizeof(buf) - 1, &fw_size, &fw_buf)) {
+ if (!*buf || *buf == '#' || *buf == '\n')
+ continue;
+ if (*buf == '[')
+ line_mode = parse_line_mode(buf, bus);
+ else if (patch_items[line_mode].parser &&
+ (codec || line_mode <= LINE_MODE_CODEC))
+ patch_items[line_mode].parser(buf, bus, &codec);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_load_patch);
+#endif /* CONFIG_SND_HDA_PATCH_LOADER */
+
+/*
+ * sysfs entries
+ */
+static struct attribute *hda_dev_attrs[] = {
+ &dev_attr_vendor_id.attr,
+ &dev_attr_subsystem_id.attr,
+ &dev_attr_revision_id.attr,
+ &dev_attr_afg.attr,
+ &dev_attr_mfg.attr,
+ &dev_attr_vendor_name.attr,
+ &dev_attr_chip_name.attr,
+ &dev_attr_modelname.attr,
+ &dev_attr_init_pin_configs.attr,
+ &dev_attr_driver_pin_configs.attr,
+#ifdef CONFIG_PM
+ &dev_attr_power_on_acct.attr,
+ &dev_attr_power_off_acct.attr,
+#endif
+#ifdef CONFIG_SND_HDA_RECONFIG
+ &dev_attr_init_verbs.attr,
+ &dev_attr_hints.attr,
+ &dev_attr_user_pin_configs.attr,
+ &dev_attr_reconfig.attr,
+ &dev_attr_clear.attr,
+#endif
+ NULL
+};
+
+static const struct attribute_group hda_dev_attr_group = {
+ .attrs = hda_dev_attrs,
+};
+
+const struct attribute_group *snd_hda_dev_attr_groups[] = {
+ &hda_dev_attr_group,
+ NULL
+};
+
+void snd_hda_sysfs_init(struct hda_codec *codec)
+{
+ mutex_init(&codec->user_mutex);
+#ifdef CONFIG_SND_HDA_RECONFIG
+ snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32);
+ snd_array_init(&codec->hints, sizeof(struct hda_hint), 32);
+ snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16);
+#endif
+}
+
+void snd_hda_sysfs_clear(struct hda_codec *codec)
+{
+#ifdef CONFIG_SND_HDA_RECONFIG
+ struct hda_hint *hint;
+ int i;
+
+ /* clear init verbs */
+ snd_array_free(&codec->init_verbs);
+ /* clear hints */
+ snd_array_for_each(&codec->hints, i, hint) {
+ kfree(hint->key); /* we don't need to free hint->val */
+ }
+ snd_array_free(&codec->hints);
+ snd_array_free(&codec->user_pins);
+#endif
+}
diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c
new file mode 100644
index 000000000..17b06f7b6
--- /dev/null
+++ b/sound/pci/hda/hda_tegra.c
@@ -0,0 +1,564 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * Implementation of primary ALSA driver code base for NVIDIA Tegra HDA.
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/string.h>
+#include <linux/pm_runtime.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+
+#include <sound/hda_codec.h>
+#include "hda_controller.h"
+
+/* Defines for Nvidia Tegra HDA support */
+#define HDA_BAR0 0x8000
+
+#define HDA_CFG_CMD 0x1004
+#define HDA_CFG_BAR0 0x1010
+
+#define HDA_ENABLE_IO_SPACE (1 << 0)
+#define HDA_ENABLE_MEM_SPACE (1 << 1)
+#define HDA_ENABLE_BUS_MASTER (1 << 2)
+#define HDA_ENABLE_SERR (1 << 8)
+#define HDA_DISABLE_INTR (1 << 10)
+#define HDA_BAR0_INIT_PROGRAM 0xFFFFFFFF
+#define HDA_BAR0_FINAL_PROGRAM (1 << 14)
+
+/* IPFS */
+#define HDA_IPFS_CONFIG 0x180
+#define HDA_IPFS_EN_FPCI 0x1
+
+#define HDA_IPFS_FPCI_BAR0 0x80
+#define HDA_FPCI_BAR0_START 0x40
+
+#define HDA_IPFS_INTR_MASK 0x188
+#define HDA_IPFS_EN_INTR (1 << 16)
+
+/* FPCI */
+#define FPCI_DBG_CFG_2 0x10F4
+#define FPCI_GCAP_NSDO_SHIFT 18
+#define FPCI_GCAP_NSDO_MASK (0x3 << FPCI_GCAP_NSDO_SHIFT)
+
+/* max number of SDs */
+#define NUM_CAPTURE_SD 1
+#define NUM_PLAYBACK_SD 1
+
+/*
+ * Tegra194 does not reflect correct number of SDO lines. Below macro
+ * is used to update the GCAP register to workaround the issue.
+ */
+#define TEGRA194_NUM_SDO_LINES 4
+
+struct hda_tegra {
+ struct azx chip;
+ struct device *dev;
+ struct reset_control *reset;
+ struct clk_bulk_data clocks[3];
+ unsigned int nclocks;
+ void __iomem *regs;
+ struct work_struct probe_work;
+};
+
+#ifdef CONFIG_PM
+static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
+module_param(power_save, bint, 0644);
+MODULE_PARM_DESC(power_save,
+ "Automatic power-saving timeout (in seconds, 0 = disable).");
+#else
+#define power_save 0
+#endif
+
+static const struct hda_controller_ops hda_tegra_ops; /* nothing special */
+
+static void hda_tegra_init(struct hda_tegra *hda)
+{
+ u32 v;
+
+ /* Enable PCI access */
+ v = readl(hda->regs + HDA_IPFS_CONFIG);
+ v |= HDA_IPFS_EN_FPCI;
+ writel(v, hda->regs + HDA_IPFS_CONFIG);
+
+ /* Enable MEM/IO space and bus master */
+ v = readl(hda->regs + HDA_CFG_CMD);
+ v &= ~HDA_DISABLE_INTR;
+ v |= HDA_ENABLE_MEM_SPACE | HDA_ENABLE_IO_SPACE |
+ HDA_ENABLE_BUS_MASTER | HDA_ENABLE_SERR;
+ writel(v, hda->regs + HDA_CFG_CMD);
+
+ writel(HDA_BAR0_INIT_PROGRAM, hda->regs + HDA_CFG_BAR0);
+ writel(HDA_BAR0_FINAL_PROGRAM, hda->regs + HDA_CFG_BAR0);
+ writel(HDA_FPCI_BAR0_START, hda->regs + HDA_IPFS_FPCI_BAR0);
+
+ v = readl(hda->regs + HDA_IPFS_INTR_MASK);
+ v |= HDA_IPFS_EN_INTR;
+ writel(v, hda->regs + HDA_IPFS_INTR_MASK);
+}
+
+/*
+ * power management
+ */
+static int __maybe_unused hda_tegra_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ int rc;
+
+ rc = pm_runtime_force_suspend(dev);
+ if (rc < 0)
+ return rc;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+ return 0;
+}
+
+static int __maybe_unused hda_tegra_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ int rc;
+
+ rc = pm_runtime_force_resume(dev);
+ if (rc < 0)
+ return rc;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+
+static int __maybe_unused hda_tegra_runtime_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+
+ if (chip && chip->running) {
+ /* enable controller wake up event */
+ azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) |
+ STATESTS_INT_MASK);
+
+ azx_stop_chip(chip);
+ azx_enter_link_reset(chip);
+ }
+ clk_bulk_disable_unprepare(hda->nclocks, hda->clocks);
+
+ return 0;
+}
+
+static int __maybe_unused hda_tegra_runtime_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+ int rc;
+
+ if (!chip->running) {
+ rc = reset_control_assert(hda->reset);
+ if (rc)
+ return rc;
+ }
+
+ rc = clk_bulk_prepare_enable(hda->nclocks, hda->clocks);
+ if (rc != 0)
+ return rc;
+ if (chip && chip->running) {
+ hda_tegra_init(hda);
+ azx_init_chip(chip, 1);
+ /* disable controller wake up event*/
+ azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) &
+ ~STATESTS_INT_MASK);
+ } else {
+ usleep_range(10, 100);
+
+ rc = reset_control_deassert(hda->reset);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops hda_tegra_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume)
+ SET_RUNTIME_PM_OPS(hda_tegra_runtime_suspend,
+ hda_tegra_runtime_resume,
+ NULL)
+};
+
+static int hda_tegra_dev_disconnect(struct snd_device *device)
+{
+ struct azx *chip = device->device_data;
+
+ chip->bus.shutdown = 1;
+ return 0;
+}
+
+/*
+ * destructor
+ */
+static int hda_tegra_dev_free(struct snd_device *device)
+{
+ struct azx *chip = device->device_data;
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+
+ cancel_work_sync(&hda->probe_work);
+ if (azx_bus(chip)->chip_init) {
+ azx_stop_all_streams(chip);
+ azx_stop_chip(chip);
+ }
+
+ azx_free_stream_pages(chip);
+ azx_free_streams(chip);
+ snd_hdac_bus_exit(azx_bus(chip));
+
+ return 0;
+}
+
+static int hda_tegra_init_chip(struct azx *chip, struct platform_device *pdev)
+{
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+ struct hdac_bus *bus = azx_bus(chip);
+ struct device *dev = hda->dev;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hda->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(hda->regs))
+ return PTR_ERR(hda->regs);
+
+ bus->remap_addr = hda->regs + HDA_BAR0;
+ bus->addr = res->start + HDA_BAR0;
+
+ hda_tegra_init(hda);
+
+ return 0;
+}
+
+static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev)
+{
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+ struct hdac_bus *bus = azx_bus(chip);
+ struct snd_card *card = chip->card;
+ int err;
+ unsigned short gcap;
+ int irq_id = platform_get_irq(pdev, 0);
+ const char *sname, *drv_name = "tegra-hda";
+ struct device_node *np = pdev->dev.of_node;
+
+ if (irq_id < 0)
+ return irq_id;
+
+ err = hda_tegra_init_chip(chip, pdev);
+ if (err)
+ return err;
+
+ err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip);
+ if (err) {
+ dev_err(chip->card->dev,
+ "unable to request IRQ %d, disabling device\n",
+ irq_id);
+ return err;
+ }
+ bus->irq = irq_id;
+ bus->dma_stop_delay = 100;
+ card->sync_irq = bus->irq;
+
+ /*
+ * Tegra194 has 4 SDO lines and the STRIPE can be used to
+ * indicate how many of the SDO lines the stream should be
+ * striped. But GCAP register does not reflect the true
+ * capability of HW. Below workaround helps to fix this.
+ *
+ * GCAP_NSDO is bits 19:18 in T_AZA_DBG_CFG_2,
+ * 0 for 1 SDO, 1 for 2 SDO, 2 for 4 SDO lines.
+ */
+ if (of_device_is_compatible(np, "nvidia,tegra194-hda")) {
+ u32 val;
+
+ dev_info(card->dev, "Override SDO lines to %u\n",
+ TEGRA194_NUM_SDO_LINES);
+
+ val = readl(hda->regs + FPCI_DBG_CFG_2) & ~FPCI_GCAP_NSDO_MASK;
+ val |= (TEGRA194_NUM_SDO_LINES >> 1) << FPCI_GCAP_NSDO_SHIFT;
+ writel(val, hda->regs + FPCI_DBG_CFG_2);
+ }
+
+ gcap = azx_readw(chip, GCAP);
+ dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
+
+ chip->align_buffer_size = 1;
+
+ /* read number of streams from GCAP register instead of using
+ * hardcoded value
+ */
+ chip->capture_streams = (gcap >> 8) & 0x0f;
+ chip->playback_streams = (gcap >> 12) & 0x0f;
+ if (!chip->playback_streams && !chip->capture_streams) {
+ /* gcap didn't give any info, switching to old method */
+ chip->playback_streams = NUM_PLAYBACK_SD;
+ chip->capture_streams = NUM_CAPTURE_SD;
+ }
+ chip->capture_index_offset = 0;
+ chip->playback_index_offset = chip->capture_streams;
+ chip->num_streams = chip->playback_streams + chip->capture_streams;
+
+ /* initialize streams */
+ err = azx_init_streams(chip);
+ if (err < 0) {
+ dev_err(card->dev, "failed to initialize streams: %d\n", err);
+ return err;
+ }
+
+ err = azx_alloc_stream_pages(chip);
+ if (err < 0) {
+ dev_err(card->dev, "failed to allocate stream pages: %d\n",
+ err);
+ return err;
+ }
+
+ /* initialize chip */
+ azx_init_chip(chip, 1);
+
+ /*
+ * Playback (for 44.1K/48K, 2-channel, 16-bps) fails with
+ * 4 SDO lines due to legacy design limitation. Following
+ * is, from HD Audio Specification (Revision 1.0a), used to
+ * control striping of the stream across multiple SDO lines
+ * for sample rates <= 48K.
+ *
+ * { ((num_channels * bits_per_sample) / number of SDOs) >= 8 }
+ *
+ * Due to legacy design issue it is recommended that above
+ * ratio must be greater than 8. Since number of SDO lines is
+ * in powers of 2, next available ratio is 16 which can be
+ * used as a limiting factor here.
+ */
+ if (of_device_is_compatible(np, "nvidia,tegra30-hda"))
+ chip->bus.core.sdo_limit = 16;
+
+ /* codec detection */
+ if (!bus->codec_mask) {
+ dev_err(card->dev, "no codecs found!\n");
+ return -ENODEV;
+ }
+
+ /* driver name */
+ strncpy(card->driver, drv_name, sizeof(card->driver));
+ /* shortname for card */
+ sname = of_get_property(np, "nvidia,model", NULL);
+ if (!sname)
+ sname = drv_name;
+ if (strlen(sname) > sizeof(card->shortname))
+ dev_info(card->dev, "truncating shortname for card\n");
+ strncpy(card->shortname, sname, sizeof(card->shortname));
+
+ /* longname for card */
+ snprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx irq %i",
+ card->shortname, bus->addr, bus->irq);
+
+ return 0;
+}
+
+/*
+ * constructor
+ */
+
+static void hda_tegra_probe_work(struct work_struct *work);
+
+static int hda_tegra_create(struct snd_card *card,
+ unsigned int driver_caps,
+ struct hda_tegra *hda)
+{
+ static const struct snd_device_ops ops = {
+ .dev_disconnect = hda_tegra_dev_disconnect,
+ .dev_free = hda_tegra_dev_free,
+ };
+ struct azx *chip;
+ int err;
+
+ chip = &hda->chip;
+
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->ops = &hda_tegra_ops;
+ chip->driver_caps = driver_caps;
+ chip->driver_type = driver_caps & 0xff;
+ chip->dev_index = 0;
+ INIT_LIST_HEAD(&chip->pcm_list);
+
+ chip->codec_probe_mask = -1;
+
+ chip->single_cmd = false;
+ chip->snoop = true;
+
+ INIT_WORK(&hda->probe_work, hda_tegra_probe_work);
+
+ err = azx_bus_init(chip, NULL);
+ if (err < 0)
+ return err;
+
+ chip->bus.core.sync_write = 0;
+ chip->bus.core.needs_damn_long_delay = 1;
+ chip->bus.core.aligned_mmio = 1;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ dev_err(card->dev, "Error creating device\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id hda_tegra_match[] = {
+ { .compatible = "nvidia,tegra30-hda" },
+ { .compatible = "nvidia,tegra194-hda" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, hda_tegra_match);
+
+static int hda_tegra_probe(struct platform_device *pdev)
+{
+ const unsigned int driver_flags = AZX_DCAPS_CORBRP_SELF_CLEAR |
+ AZX_DCAPS_PM_RUNTIME |
+ AZX_DCAPS_4K_BDLE_BOUNDARY;
+ struct snd_card *card;
+ struct azx *chip;
+ struct hda_tegra *hda;
+ int err;
+
+ hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
+ if (!hda)
+ return -ENOMEM;
+ hda->dev = &pdev->dev;
+ chip = &hda->chip;
+
+ err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &card);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Error creating card!\n");
+ return err;
+ }
+
+ hda->reset = devm_reset_control_array_get_exclusive(&pdev->dev);
+ if (IS_ERR(hda->reset)) {
+ err = PTR_ERR(hda->reset);
+ goto out_free;
+ }
+
+ hda->clocks[hda->nclocks++].id = "hda";
+ hda->clocks[hda->nclocks++].id = "hda2hdmi";
+ hda->clocks[hda->nclocks++].id = "hda2codec_2x";
+
+ err = devm_clk_bulk_get(&pdev->dev, hda->nclocks, hda->clocks);
+ if (err < 0)
+ goto out_free;
+
+ err = hda_tegra_create(card, driver_flags, hda);
+ if (err < 0)
+ goto out_free;
+ card->private_data = chip;
+
+ dev_set_drvdata(&pdev->dev, card);
+
+ pm_runtime_enable(hda->dev);
+ if (!azx_has_pm_runtime(chip))
+ pm_runtime_forbid(hda->dev);
+
+ schedule_work(&hda->probe_work);
+
+ return 0;
+
+out_free:
+ snd_card_free(card);
+ return err;
+}
+
+static void hda_tegra_probe_work(struct work_struct *work)
+{
+ struct hda_tegra *hda = container_of(work, struct hda_tegra, probe_work);
+ struct azx *chip = &hda->chip;
+ struct platform_device *pdev = to_platform_device(hda->dev);
+ int err;
+
+ pm_runtime_get_sync(hda->dev);
+ err = hda_tegra_first_init(chip, pdev);
+ if (err < 0)
+ goto out_free;
+
+ /* create codec instances */
+ err = azx_probe_codecs(chip, 8);
+ if (err < 0)
+ goto out_free;
+
+ err = azx_codec_configure(chip);
+ if (err < 0)
+ goto out_free;
+
+ err = snd_card_register(chip->card);
+ if (err < 0)
+ goto out_free;
+
+ chip->running = 1;
+ snd_hda_set_power_save(&chip->bus, power_save * 1000);
+
+ out_free:
+ pm_runtime_put(hda->dev);
+ return; /* no error return from async probe */
+}
+
+static int hda_tegra_remove(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = snd_card_free(dev_get_drvdata(&pdev->dev));
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static void hda_tegra_shutdown(struct platform_device *pdev)
+{
+ struct snd_card *card = dev_get_drvdata(&pdev->dev);
+ struct azx *chip;
+
+ if (!card)
+ return;
+ chip = card->private_data;
+ if (chip && chip->running)
+ azx_stop_chip(chip);
+}
+
+static struct platform_driver tegra_platform_hda = {
+ .driver = {
+ .name = "tegra-hda",
+ .pm = &hda_tegra_pm,
+ .of_match_table = hda_tegra_match,
+ },
+ .probe = hda_tegra_probe,
+ .remove = hda_tegra_remove,
+ .shutdown = hda_tegra_shutdown,
+};
+module_platform_driver(tegra_platform_hda);
+
+MODULE_DESCRIPTION("Tegra HDA bus driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/pci/hda/hp_x360_helper.c b/sound/pci/hda/hp_x360_helper.c
new file mode 100644
index 000000000..969542c57
--- /dev/null
+++ b/sound/pci/hda/hp_x360_helper.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Fixes for HP X360 laptops with top B&O speakers
+ * to be included from codec driver
+ */
+
+static void alc295_fixup_hp_top_speakers(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x17, 0x90170110 },
+ { }
+ };
+ static const struct coef_fw alc295_hp_speakers_coefs[] = {
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003f), WRITE_COEF(0x28, 0x1000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0004), WRITE_COEF(0x28, 0x0600), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0006), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0xc0c0), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0008), WRITE_COEF(0x28, 0xb000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x002e), WRITE_COEF(0x28, 0x0800), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x00c1), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x0320), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0039), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003b), WRITE_COEF(0x28, 0xffff), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003c), WRITE_COEF(0x28, 0xffd0), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x1dfe), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0080), WRITE_COEF(0x28, 0x0880), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x0dfe), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0018), WRITE_COEF(0x28, 0x0219), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x005d), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x9142), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c0), WRITE_COEF(0x28, 0x01ce), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c1), WRITE_COEF(0x28, 0xed0c), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c2), WRITE_COEF(0x28, 0x1c00), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c3), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c4), WRITE_COEF(0x28, 0x0200), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c5), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c6), WRITE_COEF(0x28, 0x0399), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c7), WRITE_COEF(0x28, 0x2330), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c8), WRITE_COEF(0x28, 0x1e5d), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c9), WRITE_COEF(0x28, 0x6eff), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00ca), WRITE_COEF(0x28, 0x01c0), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cb), WRITE_COEF(0x28, 0xed0c), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cc), WRITE_COEF(0x28, 0x1c00), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cd), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00ce), WRITE_COEF(0x28, 0x0200), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cf), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d0), WRITE_COEF(0x28, 0x0399), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d1), WRITE_COEF(0x28, 0x2330), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d2), WRITE_COEF(0x28, 0x1e5d), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d3), WRITE_COEF(0x28, 0x6eff), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0062), WRITE_COEF(0x28, 0x8000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0063), WRITE_COEF(0x28, 0x5f5f), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0064), WRITE_COEF(0x28, 0x1000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0065), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0066), WRITE_COEF(0x28, 0x4004), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0067), WRITE_COEF(0x28, 0x0802), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0068), WRITE_COEF(0x28, 0x890f), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0069), WRITE_COEF(0x28, 0xe021), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0070), WRITE_COEF(0x28, 0x8012), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0071), WRITE_COEF(0x28, 0x3450), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0072), WRITE_COEF(0x28, 0x0123), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0073), WRITE_COEF(0x28, 0x4543), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0074), WRITE_COEF(0x28, 0x2100), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0075), WRITE_COEF(0x28, 0x4321), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0076), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0050), WRITE_COEF(0x28, 0x8200), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x1dfe), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0051), WRITE_COEF(0x28, 0x0707), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0052), WRITE_COEF(0x28, 0x4090), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0090), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x721f), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0012), WRITE_COEF(0x28, 0xebeb), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x009e), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0060), WRITE_COEF(0x28, 0x2213), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0006), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003f), WRITE_COEF(0x28, 0x3000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0004), WRITE_COEF(0x28, 0x0500), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0040), WRITE_COEF(0x28, 0x800c), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0046), WRITE_COEF(0x28, 0xc22e), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x004b), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024),
+ WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0050), WRITE_COEF(0x28, 0x82ec), WRITE_COEF(0x29, 0xb024),
+ };
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ alc295_fixup_disable_dac3(codec, fix, action);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc_process_coef_fw(codec, alc295_hp_speakers_coefs);
+ break;
+ }
+}
diff --git a/sound/pci/hda/ideapad_s740_helper.c b/sound/pci/hda/ideapad_s740_helper.c
new file mode 100644
index 000000000..564b9086e
--- /dev/null
+++ b/sound/pci/hda/ideapad_s740_helper.c
@@ -0,0 +1,492 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Fixes for Lenovo Ideapad S740, to be included from codec driver */
+
+static const struct hda_verb alc285_ideapad_s740_coefs[] = {
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x10 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0320 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0041 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0041 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001d },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x004e },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001d },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x004e },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x002a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x002a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0046 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0046 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0044 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0044 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 },
+{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+{}
+};
+
+static void alc285_fixup_ideapad_s740_coef(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_add_verbs(codec, alc285_ideapad_s740_coefs);
+ break;
+ }
+}
diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c
new file mode 100644
index 000000000..2132b2ace
--- /dev/null
+++ b/sound/pci/hda/patch_analog.c
@@ -0,0 +1,1187 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HD audio interface patch for AD1882, AD1884, AD1981HD, AD1983, AD1984,
+ * AD1986A, AD1988
+ *
+ * Copyright (c) 2005-2007 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_beep.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+
+struct ad198x_spec {
+ struct hda_gen_spec gen;
+
+ /* for auto parser */
+ int smux_paths[4];
+ unsigned int cur_smux;
+ hda_nid_t eapd_nid;
+
+ unsigned int beep_amp; /* beep amp value, set via set_beep_amp() */
+ int num_smux_conns;
+};
+
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* additional beep mixers; the actual parameters are overwritten at build */
+static const struct snd_kcontrol_new ad_beep_mixer[] = {
+ HDA_CODEC_VOLUME("Beep Playback Volume", 0, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_BEEP("Beep Playback Switch", 0, 0, HDA_OUTPUT),
+ { } /* end */
+};
+
+#define set_beep_amp(spec, nid, idx, dir) \
+ ((spec)->beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir)) /* mono */
+#else
+#define set_beep_amp(spec, nid, idx, dir) /* NOP */
+#endif
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+static int create_beep_ctls(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec = codec->spec;
+ const struct snd_kcontrol_new *knew;
+
+ if (!spec->beep_amp)
+ return 0;
+
+ for (knew = ad_beep_mixer ; knew->name; knew++) {
+ int err;
+ struct snd_kcontrol *kctl;
+ kctl = snd_ctl_new1(knew, codec);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->private_value = spec->beep_amp;
+ err = snd_hda_ctl_add(codec, 0, kctl);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+#else
+#define create_beep_ctls(codec) 0
+#endif
+
+
+static void ad198x_power_eapd_write(struct hda_codec *codec, hda_nid_t front,
+ hda_nid_t hp)
+{
+ if (snd_hda_query_pin_caps(codec, front) & AC_PINCAP_EAPD)
+ snd_hda_codec_write(codec, front, 0, AC_VERB_SET_EAPD_BTLENABLE,
+ !codec->inv_eapd ? 0x00 : 0x02);
+ if (snd_hda_query_pin_caps(codec, hp) & AC_PINCAP_EAPD)
+ snd_hda_codec_write(codec, hp, 0, AC_VERB_SET_EAPD_BTLENABLE,
+ !codec->inv_eapd ? 0x00 : 0x02);
+}
+
+static void ad198x_power_eapd(struct hda_codec *codec)
+{
+ /* We currently only handle front, HP */
+ switch (codec->core.vendor_id) {
+ case 0x11d41882:
+ case 0x11d4882a:
+ case 0x11d41884:
+ case 0x11d41984:
+ case 0x11d41883:
+ case 0x11d4184a:
+ case 0x11d4194a:
+ case 0x11d4194b:
+ case 0x11d41988:
+ case 0x11d4198b:
+ case 0x11d4989a:
+ case 0x11d4989b:
+ ad198x_power_eapd_write(codec, 0x12, 0x11);
+ break;
+ case 0x11d41981:
+ case 0x11d41983:
+ ad198x_power_eapd_write(codec, 0x05, 0x06);
+ break;
+ case 0x11d41986:
+ ad198x_power_eapd_write(codec, 0x1b, 0x1a);
+ break;
+ }
+}
+
+static void ad198x_shutup(struct hda_codec *codec)
+{
+ snd_hda_shutup_pins(codec);
+ ad198x_power_eapd(codec);
+}
+
+#ifdef CONFIG_PM
+static int ad198x_suspend(struct hda_codec *codec)
+{
+ ad198x_shutup(codec);
+ return 0;
+}
+#endif
+
+/* follow EAPD via vmaster hook */
+static void ad_vmaster_eapd_hook(void *private_data, int enabled)
+{
+ struct hda_codec *codec = private_data;
+ struct ad198x_spec *spec = codec->spec;
+
+ if (!spec->eapd_nid)
+ return;
+ if (codec->inv_eapd)
+ enabled = !enabled;
+ snd_hda_codec_write_cache(codec, spec->eapd_nid, 0,
+ AC_VERB_SET_EAPD_BTLENABLE,
+ enabled ? 0x02 : 0x00);
+}
+
+/*
+ * Automatic parse of I/O pins from the BIOS configuration
+ */
+
+static int ad198x_auto_build_controls(struct hda_codec *codec)
+{
+ int err;
+
+ err = snd_hda_gen_build_controls(codec);
+ if (err < 0)
+ return err;
+ err = create_beep_ctls(codec);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static const struct hda_codec_ops ad198x_auto_patch_ops = {
+ .build_controls = ad198x_auto_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = snd_hda_gen_init,
+ .free = snd_hda_gen_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .check_power_status = snd_hda_gen_check_power_status,
+ .suspend = ad198x_suspend,
+#endif
+ .reboot_notify = ad198x_shutup,
+};
+
+
+static int ad198x_parse_auto_config(struct hda_codec *codec, bool indep_hp)
+{
+ struct ad198x_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ int err;
+
+ codec->spdif_status_reset = 1;
+ codec->no_trigger_sense = 1;
+ codec->no_sticky_stream = 1;
+
+ spec->gen.indep_hp = indep_hp;
+ if (!spec->gen.add_stereo_mix_input)
+ spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO;
+
+ err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0);
+ if (err < 0)
+ return err;
+ err = snd_hda_gen_parse_auto_config(codec, cfg);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+/*
+ * AD1986A specific
+ */
+
+static int alloc_ad_spec(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ codec->spec = spec;
+ snd_hda_gen_spec_init(&spec->gen);
+ codec->patch_ops = ad198x_auto_patch_ops;
+ return 0;
+}
+
+/*
+ * AD1986A fixup codes
+ */
+
+/* Lenovo N100 seems to report the reversed bit for HP jack-sensing */
+static void ad_fixup_inv_jack_detect(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct ad198x_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ codec->inv_jack_detect = 1;
+ spec->gen.keep_eapd_on = 1;
+ spec->gen.vmaster_mute.hook = ad_vmaster_eapd_hook;
+ spec->eapd_nid = 0x1b;
+ }
+}
+
+/* Toshiba Satellite L40 implements EAPD in a standard way unlike others */
+static void ad1986a_fixup_eapd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct ad198x_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ codec->inv_eapd = 0;
+ spec->gen.keep_eapd_on = 1;
+ spec->eapd_nid = 0x1b;
+ }
+}
+
+/* enable stereo-mix input for avoiding regression on KDE (bko#88251) */
+static void ad1986a_fixup_eapd_mix_in(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct ad198x_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ ad1986a_fixup_eapd(codec, fix, action);
+ spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_ENABLE;
+ }
+}
+
+enum {
+ AD1986A_FIXUP_INV_JACK_DETECT,
+ AD1986A_FIXUP_ULTRA,
+ AD1986A_FIXUP_SAMSUNG,
+ AD1986A_FIXUP_3STACK,
+ AD1986A_FIXUP_LAPTOP,
+ AD1986A_FIXUP_LAPTOP_IMIC,
+ AD1986A_FIXUP_EAPD,
+ AD1986A_FIXUP_EAPD_MIX_IN,
+ AD1986A_FIXUP_EASYNOTE,
+};
+
+static const struct hda_fixup ad1986a_fixups[] = {
+ [AD1986A_FIXUP_INV_JACK_DETECT] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad_fixup_inv_jack_detect,
+ },
+ [AD1986A_FIXUP_ULTRA] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x90170110 }, /* speaker */
+ { 0x1d, 0x90a7013e }, /* int mic */
+ {}
+ },
+ },
+ [AD1986A_FIXUP_SAMSUNG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x90170110 }, /* speaker */
+ { 0x1d, 0x90a7013e }, /* int mic */
+ { 0x20, 0x411111f0 }, /* N/A */
+ { 0x24, 0x411111f0 }, /* N/A */
+ {}
+ },
+ },
+ [AD1986A_FIXUP_3STACK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x02214021 }, /* headphone */
+ { 0x1b, 0x01014011 }, /* front */
+ { 0x1c, 0x01813030 }, /* line-in */
+ { 0x1d, 0x01a19020 }, /* rear mic */
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { 0x1f, 0x02a190f0 }, /* mic */
+ { 0x20, 0x411111f0 }, /* N/A */
+ {}
+ },
+ },
+ [AD1986A_FIXUP_LAPTOP] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x02214021 }, /* headphone */
+ { 0x1b, 0x90170110 }, /* speaker */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { 0x1f, 0x02a191f0 }, /* mic */
+ { 0x20, 0x411111f0 }, /* N/A */
+ {}
+ },
+ },
+ [AD1986A_FIXUP_LAPTOP_IMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1d, 0x90a7013e }, /* int mic */
+ {}
+ },
+ .chained_before = 1,
+ .chain_id = AD1986A_FIXUP_LAPTOP,
+ },
+ [AD1986A_FIXUP_EAPD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1986a_fixup_eapd,
+ },
+ [AD1986A_FIXUP_EAPD_MIX_IN] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1986a_fixup_eapd_mix_in,
+ },
+ [AD1986A_FIXUP_EASYNOTE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x0421402f }, /* headphone */
+ { 0x1b, 0x90170110 }, /* speaker */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x90a70130 }, /* int mic */
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { 0x1f, 0x04a19040 }, /* mic */
+ { 0x20, 0x411111f0 }, /* N/A */
+ { 0x21, 0x411111f0 }, /* N/A */
+ { 0x22, 0x411111f0 }, /* N/A */
+ { 0x23, 0x411111f0 }, /* N/A */
+ { 0x24, 0x411111f0 }, /* N/A */
+ { 0x25, 0x411111f0 }, /* N/A */
+ {}
+ },
+ .chained = true,
+ .chain_id = AD1986A_FIXUP_EAPD_MIX_IN,
+ },
+};
+
+static const struct snd_pci_quirk ad1986a_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_FIXUP_LAPTOP_IMIC),
+ SND_PCI_QUIRK(0x1043, 0x1153, "ASUS M9V", AD1986A_FIXUP_LAPTOP_IMIC),
+ SND_PCI_QUIRK(0x1043, 0x1443, "ASUS Z99He", AD1986A_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x1043, 0x1447, "ASUS A8JN", AD1986A_FIXUP_EAPD),
+ SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8100, "ASUS P5", AD1986A_FIXUP_3STACK),
+ SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8200, "ASUS M2", AD1986A_FIXUP_3STACK),
+ SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_FIXUP_3STACK),
+ SND_PCI_QUIRK(0x1179, 0xff40, "Toshiba Satellite L40", AD1986A_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x144d, 0xc01e, "FSC V2060", AD1986A_FIXUP_LAPTOP),
+ SND_PCI_QUIRK_MASK(0x144d, 0xff00, 0xc000, "Samsung", AD1986A_FIXUP_SAMSUNG),
+ SND_PCI_QUIRK(0x144d, 0xc027, "Samsung Q1", AD1986A_FIXUP_ULTRA),
+ SND_PCI_QUIRK(0x1631, 0xc022, "PackardBell EasyNote MX65", AD1986A_FIXUP_EASYNOTE),
+ SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo N100", AD1986A_FIXUP_INV_JACK_DETECT),
+ SND_PCI_QUIRK(0x17aa, 0x1011, "Lenovo M55", AD1986A_FIXUP_3STACK),
+ SND_PCI_QUIRK(0x17aa, 0x1017, "Lenovo A60", AD1986A_FIXUP_3STACK),
+ {}
+};
+
+static const struct hda_model_fixup ad1986a_fixup_models[] = {
+ { .id = AD1986A_FIXUP_3STACK, .name = "3stack" },
+ { .id = AD1986A_FIXUP_LAPTOP, .name = "laptop" },
+ { .id = AD1986A_FIXUP_LAPTOP_IMIC, .name = "laptop-imic" },
+ { .id = AD1986A_FIXUP_LAPTOP_IMIC, .name = "laptop-eapd" }, /* alias */
+ { .id = AD1986A_FIXUP_EAPD, .name = "eapd" },
+ {}
+};
+
+/*
+ */
+static int patch_ad1986a(struct hda_codec *codec)
+{
+ int err;
+ struct ad198x_spec *spec;
+ static const hda_nid_t preferred_pairs[] = {
+ 0x1a, 0x03,
+ 0x1b, 0x03,
+ 0x1c, 0x04,
+ 0x1d, 0x05,
+ 0x1e, 0x03,
+ 0
+ };
+
+ err = alloc_ad_spec(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+
+ /* AD1986A has the inverted EAPD implementation */
+ codec->inv_eapd = 1;
+
+ spec->gen.mixer_nid = 0x07;
+ spec->gen.beep_nid = 0x19;
+ set_beep_amp(spec, 0x18, 0, HDA_OUTPUT);
+
+ /* AD1986A has a hardware problem that it can't share a stream
+ * with multiple output pins. The copy of front to surrounds
+ * causes noisy or silent outputs at a certain timing, e.g.
+ * changing the volume.
+ * So, let's disable the shared stream.
+ */
+ spec->gen.multiout.no_share_stream = 1;
+ /* give fixed DAC/pin pairs */
+ spec->gen.preferred_dacs = preferred_pairs;
+
+ /* AD1986A can't manage the dynamic pin on/off smoothly */
+ spec->gen.auto_mute_via_amp = 1;
+
+ snd_hda_pick_fixup(codec, ad1986a_fixup_models, ad1986a_fixup_tbl,
+ ad1986a_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = ad198x_parse_auto_config(codec, false);
+ if (err < 0) {
+ snd_hda_gen_free(codec);
+ return err;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+
+/*
+ * AD1983 specific
+ */
+
+/*
+ * SPDIF mux control for AD1983 auto-parser
+ */
+static int ad1983_auto_smux_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad198x_spec *spec = codec->spec;
+ static const char * const texts2[] = { "PCM", "ADC" };
+ static const char * const texts3[] = { "PCM", "ADC1", "ADC2" };
+ int num_conns = spec->num_smux_conns;
+
+ if (num_conns == 2)
+ return snd_hda_enum_helper_info(kcontrol, uinfo, 2, texts2);
+ else if (num_conns == 3)
+ return snd_hda_enum_helper_info(kcontrol, uinfo, 3, texts3);
+ else
+ return -EINVAL;
+}
+
+static int ad1983_auto_smux_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad198x_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->cur_smux;
+ return 0;
+}
+
+static int ad1983_auto_smux_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad198x_spec *spec = codec->spec;
+ unsigned int val = ucontrol->value.enumerated.item[0];
+ hda_nid_t dig_out = spec->gen.multiout.dig_out_nid;
+ int num_conns = spec->num_smux_conns;
+
+ if (val >= num_conns)
+ return -EINVAL;
+ if (spec->cur_smux == val)
+ return 0;
+ spec->cur_smux = val;
+ snd_hda_codec_write_cache(codec, dig_out, 0,
+ AC_VERB_SET_CONNECT_SEL, val);
+ return 1;
+}
+
+static const struct snd_kcontrol_new ad1983_auto_smux_mixer = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "IEC958 Playback Source",
+ .info = ad1983_auto_smux_enum_info,
+ .get = ad1983_auto_smux_enum_get,
+ .put = ad1983_auto_smux_enum_put,
+};
+
+static int ad1983_add_spdif_mux_ctl(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec = codec->spec;
+ hda_nid_t dig_out = spec->gen.multiout.dig_out_nid;
+ int num_conns;
+
+ if (!dig_out)
+ return 0;
+ num_conns = snd_hda_get_num_conns(codec, dig_out);
+ if (num_conns != 2 && num_conns != 3)
+ return 0;
+ spec->num_smux_conns = num_conns;
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &ad1983_auto_smux_mixer))
+ return -ENOMEM;
+ return 0;
+}
+
+static int patch_ad1983(struct hda_codec *codec)
+{
+ static const hda_nid_t conn_0c[] = { 0x08 };
+ static const hda_nid_t conn_0d[] = { 0x09 };
+ struct ad198x_spec *spec;
+ int err;
+
+ err = alloc_ad_spec(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+
+ spec->gen.mixer_nid = 0x0e;
+ spec->gen.beep_nid = 0x10;
+ set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+ /* limit the loopback routes not to confuse the parser */
+ snd_hda_override_conn_list(codec, 0x0c, ARRAY_SIZE(conn_0c), conn_0c);
+ snd_hda_override_conn_list(codec, 0x0d, ARRAY_SIZE(conn_0d), conn_0d);
+
+ err = ad198x_parse_auto_config(codec, false);
+ if (err < 0)
+ goto error;
+ err = ad1983_add_spdif_mux_ctl(codec);
+ if (err < 0)
+ goto error;
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+
+/*
+ * AD1981 HD specific
+ */
+
+static void ad1981_fixup_hp_eapd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct ad198x_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gen.vmaster_mute.hook = ad_vmaster_eapd_hook;
+ spec->eapd_nid = 0x05;
+ }
+}
+
+/* set the upper-limit for mixer amp to 0dB for avoiding the possible
+ * damage by overloading
+ */
+static void ad1981_fixup_amp_override(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ snd_hda_override_amp_caps(codec, 0x11, HDA_INPUT,
+ (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (1 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+enum {
+ AD1981_FIXUP_AMP_OVERRIDE,
+ AD1981_FIXUP_HP_EAPD,
+};
+
+static const struct hda_fixup ad1981_fixups[] = {
+ [AD1981_FIXUP_AMP_OVERRIDE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1981_fixup_amp_override,
+ },
+ [AD1981_FIXUP_HP_EAPD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1981_fixup_hp_eapd,
+ .chained = true,
+ .chain_id = AD1981_FIXUP_AMP_OVERRIDE,
+ },
+};
+
+static const struct snd_pci_quirk ad1981_fixup_tbl[] = {
+ SND_PCI_QUIRK_VENDOR(0x1014, "Lenovo", AD1981_FIXUP_AMP_OVERRIDE),
+ SND_PCI_QUIRK_VENDOR(0x103c, "HP", AD1981_FIXUP_HP_EAPD),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo", AD1981_FIXUP_AMP_OVERRIDE),
+ /* HP nx6320 (reversed SSID, H/W bug) */
+ SND_PCI_QUIRK(0x30b0, 0x103c, "HP nx6320", AD1981_FIXUP_HP_EAPD),
+ {}
+};
+
+static int patch_ad1981(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec;
+ int err;
+
+ err = alloc_ad_spec(codec);
+ if (err < 0)
+ return -ENOMEM;
+ spec = codec->spec;
+
+ spec->gen.mixer_nid = 0x0e;
+ spec->gen.beep_nid = 0x10;
+ set_beep_amp(spec, 0x0d, 0, HDA_OUTPUT);
+
+ snd_hda_pick_fixup(codec, NULL, ad1981_fixup_tbl, ad1981_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = ad198x_parse_auto_config(codec, false);
+ if (err < 0)
+ goto error;
+ err = ad1983_add_spdif_mux_ctl(codec);
+ if (err < 0)
+ goto error;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+
+/*
+ * AD1988
+ *
+ * Output pins and routes
+ *
+ * Pin Mix Sel DAC (*)
+ * port-A 0x11 (mute/hp) <- 0x22 <- 0x37 <- 03/04/06
+ * port-B 0x14 (mute/hp) <- 0x2b <- 0x30 <- 03/04/06
+ * port-C 0x15 (mute) <- 0x2c <- 0x31 <- 05/0a
+ * port-D 0x12 (mute/hp) <- 0x29 <- 04
+ * port-E 0x17 (mute/hp) <- 0x26 <- 0x32 <- 05/0a
+ * port-F 0x16 (mute) <- 0x2a <- 06
+ * port-G 0x24 (mute) <- 0x27 <- 05
+ * port-H 0x25 (mute) <- 0x28 <- 0a
+ * mono 0x13 (mute/amp)<- 0x1e <- 0x36 <- 03/04/06
+ *
+ * DAC0 = 03h, DAC1 = 04h, DAC2 = 05h, DAC3 = 06h, DAC4 = 0ah
+ * (*) DAC2/3/4 are swapped to DAC3/4/2 on AD198A rev.2 due to a h/w bug.
+ *
+ * Input pins and routes
+ *
+ * pin boost mix input # / adc input #
+ * port-A 0x11 -> 0x38 -> mix 2, ADC 0
+ * port-B 0x14 -> 0x39 -> mix 0, ADC 1
+ * port-C 0x15 -> 0x3a -> 33:0 - mix 1, ADC 2
+ * port-D 0x12 -> 0x3d -> mix 3, ADC 8
+ * port-E 0x17 -> 0x3c -> 34:0 - mix 4, ADC 4
+ * port-F 0x16 -> 0x3b -> mix 5, ADC 3
+ * port-G 0x24 -> N/A -> 33:1 - mix 1, 34:1 - mix 4, ADC 6
+ * port-H 0x25 -> N/A -> 33:2 - mix 1, 34:2 - mix 4, ADC 7
+ *
+ *
+ * DAC assignment
+ * 6stack - front/surr/CLFE/side/opt DACs - 04/06/05/0a/03
+ * 3stack - front/surr/CLFE/opt DACs - 04/05/0a/03
+ *
+ * Inputs of Analog Mix (0x20)
+ * 0:Port-B (front mic)
+ * 1:Port-C/G/H (line-in)
+ * 2:Port-A
+ * 3:Port-D (line-in/2)
+ * 4:Port-E/G/H (mic-in)
+ * 5:Port-F (mic2-in)
+ * 6:CD
+ * 7:Beep
+ *
+ * ADC selection
+ * 0:Port-A
+ * 1:Port-B (front mic-in)
+ * 2:Port-C (line-in)
+ * 3:Port-F (mic2-in)
+ * 4:Port-E (mic-in)
+ * 5:CD
+ * 6:Port-G
+ * 7:Port-H
+ * 8:Port-D (line-in/2)
+ * 9:Mix
+ *
+ * Proposed pin assignments by the datasheet
+ *
+ * 6-stack
+ * Port-A front headphone
+ * B front mic-in
+ * C rear line-in
+ * D rear front-out
+ * E rear mic-in
+ * F rear surround
+ * G rear CLFE
+ * H rear side
+ *
+ * 3-stack
+ * Port-A front headphone
+ * B front mic
+ * C rear line-in/surround
+ * D rear front-out
+ * E rear mic-in/CLFE
+ *
+ * laptop
+ * Port-A headphone
+ * B mic-in
+ * C docking station
+ * D internal speaker (with EAPD)
+ * E/F quad mic array
+ */
+
+static int ad1988_auto_smux_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad198x_spec *spec = codec->spec;
+ static const char * const texts[] = {
+ "PCM", "ADC1", "ADC2", "ADC3",
+ };
+ int num_conns = spec->num_smux_conns;
+
+ if (num_conns > 4)
+ num_conns = 4;
+ return snd_hda_enum_helper_info(kcontrol, uinfo, num_conns, texts);
+}
+
+static int ad1988_auto_smux_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad198x_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->cur_smux;
+ return 0;
+}
+
+static int ad1988_auto_smux_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad198x_spec *spec = codec->spec;
+ unsigned int val = ucontrol->value.enumerated.item[0];
+ struct nid_path *path;
+ int num_conns = spec->num_smux_conns;
+
+ if (val >= num_conns)
+ return -EINVAL;
+ if (spec->cur_smux == val)
+ return 0;
+
+ mutex_lock(&codec->control_mutex);
+ path = snd_hda_get_path_from_idx(codec,
+ spec->smux_paths[spec->cur_smux]);
+ if (path)
+ snd_hda_activate_path(codec, path, false, true);
+ path = snd_hda_get_path_from_idx(codec, spec->smux_paths[val]);
+ if (path)
+ snd_hda_activate_path(codec, path, true, true);
+ spec->cur_smux = val;
+ mutex_unlock(&codec->control_mutex);
+ return 1;
+}
+
+static const struct snd_kcontrol_new ad1988_auto_smux_mixer = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "IEC958 Playback Source",
+ .info = ad1988_auto_smux_enum_info,
+ .get = ad1988_auto_smux_enum_get,
+ .put = ad1988_auto_smux_enum_put,
+};
+
+static int ad1988_auto_init(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec = codec->spec;
+ int i, err;
+
+ err = snd_hda_gen_init(codec);
+ if (err < 0)
+ return err;
+ if (!spec->gen.autocfg.dig_outs)
+ return 0;
+
+ for (i = 0; i < 4; i++) {
+ struct nid_path *path;
+ path = snd_hda_get_path_from_idx(codec, spec->smux_paths[i]);
+ if (path)
+ snd_hda_activate_path(codec, path, path->active, false);
+ }
+
+ return 0;
+}
+
+static int ad1988_add_spdif_mux_ctl(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec = codec->spec;
+ int i, num_conns;
+ /* we create four static faked paths, since AD codecs have odd
+ * widget connections regarding the SPDIF out source
+ */
+ static const struct nid_path fake_paths[4] = {
+ {
+ .depth = 3,
+ .path = { 0x02, 0x1d, 0x1b },
+ .idx = { 0, 0, 0 },
+ .multi = { 0, 0, 0 },
+ },
+ {
+ .depth = 4,
+ .path = { 0x08, 0x0b, 0x1d, 0x1b },
+ .idx = { 0, 0, 1, 0 },
+ .multi = { 0, 1, 0, 0 },
+ },
+ {
+ .depth = 4,
+ .path = { 0x09, 0x0b, 0x1d, 0x1b },
+ .idx = { 0, 1, 1, 0 },
+ .multi = { 0, 1, 0, 0 },
+ },
+ {
+ .depth = 4,
+ .path = { 0x0f, 0x0b, 0x1d, 0x1b },
+ .idx = { 0, 2, 1, 0 },
+ .multi = { 0, 1, 0, 0 },
+ },
+ };
+
+ /* SPDIF source mux appears to be present only on AD1988A */
+ if (!spec->gen.autocfg.dig_outs ||
+ get_wcaps_type(get_wcaps(codec, 0x1d)) != AC_WID_AUD_MIX)
+ return 0;
+
+ num_conns = snd_hda_get_num_conns(codec, 0x0b) + 1;
+ if (num_conns != 3 && num_conns != 4)
+ return 0;
+ spec->num_smux_conns = num_conns;
+
+ for (i = 0; i < num_conns; i++) {
+ struct nid_path *path = snd_array_new(&spec->gen.paths);
+ if (!path)
+ return -ENOMEM;
+ *path = fake_paths[i];
+ if (!i)
+ path->active = 1;
+ spec->smux_paths[i] = snd_hda_get_path_idx(codec, path);
+ }
+
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &ad1988_auto_smux_mixer))
+ return -ENOMEM;
+
+ codec->patch_ops.init = ad1988_auto_init;
+
+ return 0;
+}
+
+/*
+ */
+
+enum {
+ AD1988_FIXUP_6STACK_DIG,
+};
+
+static const struct hda_fixup ad1988_fixups[] = {
+ [AD1988_FIXUP_6STACK_DIG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x11, 0x02214130 }, /* front-hp */
+ { 0x12, 0x01014010 }, /* line-out */
+ { 0x14, 0x02a19122 }, /* front-mic */
+ { 0x15, 0x01813021 }, /* line-in */
+ { 0x16, 0x01011012 }, /* line-out */
+ { 0x17, 0x01a19020 }, /* mic */
+ { 0x1b, 0x0145f1f0 }, /* SPDIF */
+ { 0x24, 0x01016011 }, /* line-out */
+ { 0x25, 0x01012013 }, /* line-out */
+ { }
+ }
+ },
+};
+
+static const struct hda_model_fixup ad1988_fixup_models[] = {
+ { .id = AD1988_FIXUP_6STACK_DIG, .name = "6stack-dig" },
+ {}
+};
+
+static int patch_ad1988(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec;
+ int err;
+
+ err = alloc_ad_spec(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+
+ spec->gen.mixer_nid = 0x20;
+ spec->gen.mixer_merge_nid = 0x21;
+ spec->gen.beep_nid = 0x10;
+ set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+ snd_hda_pick_fixup(codec, ad1988_fixup_models, NULL, ad1988_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = ad198x_parse_auto_config(codec, true);
+ if (err < 0)
+ goto error;
+ err = ad1988_add_spdif_mux_ctl(codec);
+ if (err < 0)
+ goto error;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+
+/*
+ * AD1884 / AD1984
+ *
+ * port-B - front line/mic-in
+ * port-E - aux in/out
+ * port-F - aux in/out
+ * port-C - rear line/mic-in
+ * port-D - rear line/hp-out
+ * port-A - front line/hp-out
+ *
+ * AD1984 = AD1884 + two digital mic-ins
+ *
+ * AD1883 / AD1884A / AD1984A / AD1984B
+ *
+ * port-B (0x14) - front mic-in
+ * port-E (0x1c) - rear mic-in
+ * port-F (0x16) - CD / ext out
+ * port-C (0x15) - rear line-in
+ * port-D (0x12) - rear line-out
+ * port-A (0x11) - front hp-out
+ *
+ * AD1984A = AD1884A + digital-mic
+ * AD1883 = equivalent with AD1984A
+ * AD1984B = AD1984A + extra SPDIF-out
+ */
+
+/* set the upper-limit for mixer amp to 0dB for avoiding the possible
+ * damage by overloading
+ */
+static void ad1884_fixup_amp_override(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ snd_hda_override_amp_caps(codec, 0x20, HDA_INPUT,
+ (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (1 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+/* toggle GPIO1 according to the mute state */
+static void ad1884_vmaster_hp_gpio_hook(void *private_data, int enabled)
+{
+ struct hda_codec *codec = private_data;
+ struct ad198x_spec *spec = codec->spec;
+
+ if (spec->eapd_nid)
+ ad_vmaster_eapd_hook(private_data, enabled);
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA,
+ enabled ? 0x00 : 0x02);
+}
+
+static void ad1884_fixup_hp_eapd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct ad198x_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->gen.vmaster_mute.hook = ad1884_vmaster_hp_gpio_hook;
+ spec->gen.own_eapd_ctl = 1;
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_MASK, 0x02);
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DIRECTION, 0x02);
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, 0x02);
+ break;
+ case HDA_FIXUP_ACT_PROBE:
+ if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
+ spec->eapd_nid = spec->gen.autocfg.line_out_pins[0];
+ else
+ spec->eapd_nid = spec->gen.autocfg.speaker_pins[0];
+ break;
+ }
+}
+
+static void ad1884_fixup_thinkpad(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct ad198x_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gen.keep_eapd_on = 1;
+ spec->gen.vmaster_mute.hook = ad_vmaster_eapd_hook;
+ spec->eapd_nid = 0x12;
+ /* Analog PC Beeper - allow firmware/ACPI beeps */
+ spec->beep_amp = HDA_COMPOSE_AMP_VAL(0x20, 3, 3, HDA_INPUT);
+ spec->gen.beep_nid = 0; /* no digital beep */
+ }
+}
+
+/* set magic COEFs for dmic */
+static const struct hda_verb ad1884_dmic_init_verbs[] = {
+ {0x01, AC_VERB_SET_COEF_INDEX, 0x13f7},
+ {0x01, AC_VERB_SET_PROC_COEF, 0x08},
+ {}
+};
+
+enum {
+ AD1884_FIXUP_AMP_OVERRIDE,
+ AD1884_FIXUP_HP_EAPD,
+ AD1884_FIXUP_DMIC_COEF,
+ AD1884_FIXUP_THINKPAD,
+ AD1884_FIXUP_HP_TOUCHSMART,
+};
+
+static const struct hda_fixup ad1884_fixups[] = {
+ [AD1884_FIXUP_AMP_OVERRIDE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1884_fixup_amp_override,
+ },
+ [AD1884_FIXUP_HP_EAPD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1884_fixup_hp_eapd,
+ .chained = true,
+ .chain_id = AD1884_FIXUP_AMP_OVERRIDE,
+ },
+ [AD1884_FIXUP_DMIC_COEF] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = ad1884_dmic_init_verbs,
+ },
+ [AD1884_FIXUP_THINKPAD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = ad1884_fixup_thinkpad,
+ .chained = true,
+ .chain_id = AD1884_FIXUP_DMIC_COEF,
+ },
+ [AD1884_FIXUP_HP_TOUCHSMART] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = ad1884_dmic_init_verbs,
+ .chained = true,
+ .chain_id = AD1884_FIXUP_HP_EAPD,
+ },
+};
+
+static const struct snd_pci_quirk ad1884_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x103c, 0x2a82, "HP Touchsmart", AD1884_FIXUP_HP_TOUCHSMART),
+ SND_PCI_QUIRK_VENDOR(0x103c, "HP", AD1884_FIXUP_HP_EAPD),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo Thinkpad", AD1884_FIXUP_THINKPAD),
+ {}
+};
+
+
+static int patch_ad1884(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec;
+ int err;
+
+ err = alloc_ad_spec(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+
+ spec->gen.mixer_nid = 0x20;
+ spec->gen.mixer_merge_nid = 0x21;
+ spec->gen.beep_nid = 0x10;
+ set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+ snd_hda_pick_fixup(codec, NULL, ad1884_fixup_tbl, ad1884_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = ad198x_parse_auto_config(codec, true);
+ if (err < 0)
+ goto error;
+ err = ad1983_add_spdif_mux_ctl(codec);
+ if (err < 0)
+ goto error;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+/*
+ * AD1882 / AD1882A
+ *
+ * port-A - front hp-out
+ * port-B - front mic-in
+ * port-C - rear line-in, shared surr-out (3stack)
+ * port-D - rear line-out
+ * port-E - rear mic-in, shared clfe-out (3stack)
+ * port-F - rear surr-out (6stack)
+ * port-G - rear clfe-out (6stack)
+ */
+
+static int patch_ad1882(struct hda_codec *codec)
+{
+ struct ad198x_spec *spec;
+ int err;
+
+ err = alloc_ad_spec(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+
+ spec->gen.mixer_nid = 0x20;
+ spec->gen.mixer_merge_nid = 0x21;
+ spec->gen.beep_nid = 0x10;
+ set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+ err = ad198x_parse_auto_config(codec, true);
+ if (err < 0)
+ goto error;
+ err = ad1988_add_spdif_mux_ctl(codec);
+ if (err < 0)
+ goto error;
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_analog[] = {
+ HDA_CODEC_ENTRY(0x11d4184a, "AD1884A", patch_ad1884),
+ HDA_CODEC_ENTRY(0x11d41882, "AD1882", patch_ad1882),
+ HDA_CODEC_ENTRY(0x11d41883, "AD1883", patch_ad1884),
+ HDA_CODEC_ENTRY(0x11d41884, "AD1884", patch_ad1884),
+ HDA_CODEC_ENTRY(0x11d4194a, "AD1984A", patch_ad1884),
+ HDA_CODEC_ENTRY(0x11d4194b, "AD1984B", patch_ad1884),
+ HDA_CODEC_ENTRY(0x11d41981, "AD1981", patch_ad1981),
+ HDA_CODEC_ENTRY(0x11d41983, "AD1983", patch_ad1983),
+ HDA_CODEC_ENTRY(0x11d41984, "AD1984", patch_ad1884),
+ HDA_CODEC_ENTRY(0x11d41986, "AD1986A", patch_ad1986a),
+ HDA_CODEC_ENTRY(0x11d41988, "AD1988", patch_ad1988),
+ HDA_CODEC_ENTRY(0x11d4198b, "AD1988B", patch_ad1988),
+ HDA_CODEC_ENTRY(0x11d4882a, "AD1882A", patch_ad1882),
+ HDA_CODEC_ENTRY(0x11d4989a, "AD1989A", patch_ad1988),
+ HDA_CODEC_ENTRY(0x11d4989b, "AD1989B", patch_ad1988),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_analog);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Analog Devices HD-audio codec");
+
+static struct hda_codec_driver analog_driver = {
+ .id = snd_hda_id_analog,
+};
+
+module_hda_codec_driver(analog_driver);
diff --git a/sound/pci/hda/patch_ca0110.c b/sound/pci/hda/patch_ca0110.c
new file mode 100644
index 000000000..1818ce67f
--- /dev/null
+++ b/sound/pci/hda/patch_ca0110.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HD audio interface patch for Creative X-Fi CA0110-IBG chip
+ *
+ * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+
+static const struct hda_codec_ops ca0110_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = snd_hda_gen_init,
+ .free = snd_hda_gen_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+};
+
+static int ca0110_parse_auto_config(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err;
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->autocfg, NULL, 0);
+ if (err < 0)
+ return err;
+ err = snd_hda_gen_parse_auto_config(codec, &spec->autocfg);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+
+static int patch_ca0110(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec;
+ int err;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ snd_hda_gen_spec_init(spec);
+ codec->spec = spec;
+ codec->patch_ops = ca0110_patch_ops;
+
+ spec->multi_cap_vol = 1;
+ codec->bus->core.needs_damn_long_delay = 1;
+
+ err = ca0110_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_ca0110[] = {
+ HDA_CODEC_ENTRY(0x1102000a, "CA0110-IBG", patch_ca0110),
+ HDA_CODEC_ENTRY(0x1102000b, "CA0110-IBG", patch_ca0110),
+ HDA_CODEC_ENTRY(0x1102000d, "SB0880 X-Fi", patch_ca0110),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_ca0110);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Creative CA0110-IBG HD-audio codec");
+
+static struct hda_codec_driver ca0110_driver = {
+ .id = snd_hda_id_ca0110,
+};
+
+module_hda_codec_driver(ca0110_driver);
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c
new file mode 100644
index 000000000..6d67cca4c
--- /dev/null
+++ b/sound/pci/hda/patch_ca0132.c
@@ -0,0 +1,9972 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HD audio interface patch for Creative CA0132 chip
+ *
+ * Copyright (c) 2011, Creative Technology Ltd.
+ *
+ * Based on patch_ca0110.c
+ * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+
+#include "ca0132_regs.h"
+
+/* Enable this to see controls for tuning purpose. */
+/*#define ENABLE_TUNING_CONTROLS*/
+
+#ifdef ENABLE_TUNING_CONTROLS
+#include <sound/tlv.h>
+#endif
+
+#define FLOAT_ZERO 0x00000000
+#define FLOAT_ONE 0x3f800000
+#define FLOAT_TWO 0x40000000
+#define FLOAT_THREE 0x40400000
+#define FLOAT_FIVE 0x40a00000
+#define FLOAT_SIX 0x40c00000
+#define FLOAT_EIGHT 0x41000000
+#define FLOAT_MINUS_5 0xc0a00000
+
+#define UNSOL_TAG_DSP 0x16
+
+#define DSP_DMA_WRITE_BUFLEN_INIT (1UL<<18)
+#define DSP_DMA_WRITE_BUFLEN_OVLY (1UL<<15)
+
+#define DMA_TRANSFER_FRAME_SIZE_NWORDS 8
+#define DMA_TRANSFER_MAX_FRAME_SIZE_NWORDS 32
+#define DMA_OVERLAY_FRAME_SIZE_NWORDS 2
+
+#define MASTERCONTROL 0x80
+#define MASTERCONTROL_ALLOC_DMA_CHAN 10
+#define MASTERCONTROL_QUERY_SPEAKER_EQ_ADDRESS 60
+
+#define WIDGET_CHIP_CTRL 0x15
+#define WIDGET_DSP_CTRL 0x16
+
+#define MEM_CONNID_MICIN1 3
+#define MEM_CONNID_MICIN2 5
+#define MEM_CONNID_MICOUT1 12
+#define MEM_CONNID_MICOUT2 14
+#define MEM_CONNID_WUH 10
+#define MEM_CONNID_DSP 16
+#define MEM_CONNID_DMIC 100
+
+#define SCP_SET 0
+#define SCP_GET 1
+
+#define EFX_FILE "ctefx.bin"
+#define DESKTOP_EFX_FILE "ctefx-desktop.bin"
+#define R3DI_EFX_FILE "ctefx-r3di.bin"
+
+#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP
+MODULE_FIRMWARE(EFX_FILE);
+MODULE_FIRMWARE(DESKTOP_EFX_FILE);
+MODULE_FIRMWARE(R3DI_EFX_FILE);
+#endif
+
+static const char *const dirstr[2] = { "Playback", "Capture" };
+
+#define NUM_OF_OUTPUTS 2
+static const char *const out_type_str[2] = { "Speakers", "Headphone" };
+enum {
+ SPEAKER_OUT,
+ HEADPHONE_OUT,
+};
+
+enum {
+ DIGITAL_MIC,
+ LINE_MIC_IN
+};
+
+/* Strings for Input Source Enum Control */
+static const char *const in_src_str[3] = { "Microphone", "Line In", "Front Microphone" };
+#define IN_SRC_NUM_OF_INPUTS 3
+enum {
+ REAR_MIC,
+ REAR_LINE_IN,
+ FRONT_MIC,
+};
+
+enum {
+#define VNODE_START_NID 0x80
+ VNID_SPK = VNODE_START_NID, /* Speaker vnid */
+ VNID_MIC,
+ VNID_HP_SEL,
+ VNID_AMIC1_SEL,
+ VNID_HP_ASEL,
+ VNID_AMIC1_ASEL,
+ VNODE_END_NID,
+#define VNODES_COUNT (VNODE_END_NID - VNODE_START_NID)
+
+#define EFFECT_START_NID 0x90
+#define OUT_EFFECT_START_NID EFFECT_START_NID
+ SURROUND = OUT_EFFECT_START_NID,
+ CRYSTALIZER,
+ DIALOG_PLUS,
+ SMART_VOLUME,
+ X_BASS,
+ EQUALIZER,
+ OUT_EFFECT_END_NID,
+#define OUT_EFFECTS_COUNT (OUT_EFFECT_END_NID - OUT_EFFECT_START_NID)
+
+#define IN_EFFECT_START_NID OUT_EFFECT_END_NID
+ ECHO_CANCELLATION = IN_EFFECT_START_NID,
+ VOICE_FOCUS,
+ MIC_SVM,
+ NOISE_REDUCTION,
+ IN_EFFECT_END_NID,
+#define IN_EFFECTS_COUNT (IN_EFFECT_END_NID - IN_EFFECT_START_NID)
+
+ VOICEFX = IN_EFFECT_END_NID,
+ PLAY_ENHANCEMENT,
+ CRYSTAL_VOICE,
+ EFFECT_END_NID,
+ OUTPUT_SOURCE_ENUM,
+ INPUT_SOURCE_ENUM,
+ XBASS_XOVER,
+ EQ_PRESET_ENUM,
+ SMART_VOLUME_ENUM,
+ MIC_BOOST_ENUM,
+ AE5_HEADPHONE_GAIN_ENUM,
+ AE5_SOUND_FILTER_ENUM,
+ ZXR_HEADPHONE_GAIN,
+ SPEAKER_CHANNEL_CFG_ENUM,
+ SPEAKER_FULL_RANGE_FRONT,
+ SPEAKER_FULL_RANGE_REAR,
+ BASS_REDIRECTION,
+ BASS_REDIRECTION_XOVER,
+#define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID)
+};
+
+/* Effects values size*/
+#define EFFECT_VALS_MAX_COUNT 12
+
+/*
+ * Default values for the effect slider controls, they are in order of their
+ * effect NID's. Surround, Crystalizer, Dialog Plus, Smart Volume, and then
+ * X-bass.
+ */
+static const unsigned int effect_slider_defaults[] = {67, 65, 50, 74, 50};
+/* Amount of effect level sliders for ca0132_alt controls. */
+#define EFFECT_LEVEL_SLIDERS 5
+
+/* Latency introduced by DSP blocks in milliseconds. */
+#define DSP_CAPTURE_INIT_LATENCY 0
+#define DSP_CRYSTAL_VOICE_LATENCY 124
+#define DSP_PLAYBACK_INIT_LATENCY 13
+#define DSP_PLAY_ENHANCEMENT_LATENCY 30
+#define DSP_SPEAKER_OUT_LATENCY 7
+
+struct ct_effect {
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ hda_nid_t nid;
+ int mid; /*effect module ID*/
+ int reqs[EFFECT_VALS_MAX_COUNT]; /*effect module request*/
+ int direct; /* 0:output; 1:input*/
+ int params; /* number of default non-on/off params */
+ /*effect default values, 1st is on/off. */
+ unsigned int def_vals[EFFECT_VALS_MAX_COUNT];
+};
+
+#define EFX_DIR_OUT 0
+#define EFX_DIR_IN 1
+
+static const struct ct_effect ca0132_effects[EFFECTS_COUNT] = {
+ { .name = "Surround",
+ .nid = SURROUND,
+ .mid = 0x96,
+ .reqs = {0, 1},
+ .direct = EFX_DIR_OUT,
+ .params = 1,
+ .def_vals = {0x3F800000, 0x3F2B851F}
+ },
+ { .name = "Crystalizer",
+ .nid = CRYSTALIZER,
+ .mid = 0x96,
+ .reqs = {7, 8},
+ .direct = EFX_DIR_OUT,
+ .params = 1,
+ .def_vals = {0x3F800000, 0x3F266666}
+ },
+ { .name = "Dialog Plus",
+ .nid = DIALOG_PLUS,
+ .mid = 0x96,
+ .reqs = {2, 3},
+ .direct = EFX_DIR_OUT,
+ .params = 1,
+ .def_vals = {0x00000000, 0x3F000000}
+ },
+ { .name = "Smart Volume",
+ .nid = SMART_VOLUME,
+ .mid = 0x96,
+ .reqs = {4, 5, 6},
+ .direct = EFX_DIR_OUT,
+ .params = 2,
+ .def_vals = {0x3F800000, 0x3F3D70A4, 0x00000000}
+ },
+ { .name = "X-Bass",
+ .nid = X_BASS,
+ .mid = 0x96,
+ .reqs = {24, 23, 25},
+ .direct = EFX_DIR_OUT,
+ .params = 2,
+ .def_vals = {0x3F800000, 0x42A00000, 0x3F000000}
+ },
+ { .name = "Equalizer",
+ .nid = EQUALIZER,
+ .mid = 0x96,
+ .reqs = {9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20},
+ .direct = EFX_DIR_OUT,
+ .params = 11,
+ .def_vals = {0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000}
+ },
+ { .name = "Echo Cancellation",
+ .nid = ECHO_CANCELLATION,
+ .mid = 0x95,
+ .reqs = {0, 1, 2, 3},
+ .direct = EFX_DIR_IN,
+ .params = 3,
+ .def_vals = {0x00000000, 0x3F3A9692, 0x00000000, 0x00000000}
+ },
+ { .name = "Voice Focus",
+ .nid = VOICE_FOCUS,
+ .mid = 0x95,
+ .reqs = {6, 7, 8, 9},
+ .direct = EFX_DIR_IN,
+ .params = 3,
+ .def_vals = {0x3F800000, 0x3D7DF3B6, 0x41F00000, 0x41F00000}
+ },
+ { .name = "Mic SVM",
+ .nid = MIC_SVM,
+ .mid = 0x95,
+ .reqs = {44, 45},
+ .direct = EFX_DIR_IN,
+ .params = 1,
+ .def_vals = {0x00000000, 0x3F3D70A4}
+ },
+ { .name = "Noise Reduction",
+ .nid = NOISE_REDUCTION,
+ .mid = 0x95,
+ .reqs = {4, 5},
+ .direct = EFX_DIR_IN,
+ .params = 1,
+ .def_vals = {0x3F800000, 0x3F000000}
+ },
+ { .name = "VoiceFX",
+ .nid = VOICEFX,
+ .mid = 0x95,
+ .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18},
+ .direct = EFX_DIR_IN,
+ .params = 8,
+ .def_vals = {0x00000000, 0x43C80000, 0x44AF0000, 0x44FA0000,
+ 0x3F800000, 0x3F800000, 0x3F800000, 0x00000000,
+ 0x00000000}
+ }
+};
+
+/* Tuning controls */
+#ifdef ENABLE_TUNING_CONTROLS
+
+enum {
+#define TUNING_CTL_START_NID 0xC0
+ WEDGE_ANGLE = TUNING_CTL_START_NID,
+ SVM_LEVEL,
+ EQUALIZER_BAND_0,
+ EQUALIZER_BAND_1,
+ EQUALIZER_BAND_2,
+ EQUALIZER_BAND_3,
+ EQUALIZER_BAND_4,
+ EQUALIZER_BAND_5,
+ EQUALIZER_BAND_6,
+ EQUALIZER_BAND_7,
+ EQUALIZER_BAND_8,
+ EQUALIZER_BAND_9,
+ TUNING_CTL_END_NID
+#define TUNING_CTLS_COUNT (TUNING_CTL_END_NID - TUNING_CTL_START_NID)
+};
+
+struct ct_tuning_ctl {
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ hda_nid_t parent_nid;
+ hda_nid_t nid;
+ int mid; /*effect module ID*/
+ int req; /*effect module request*/
+ int direct; /* 0:output; 1:input*/
+ unsigned int def_val;/*effect default values*/
+};
+
+static const struct ct_tuning_ctl ca0132_tuning_ctls[] = {
+ { .name = "Wedge Angle",
+ .parent_nid = VOICE_FOCUS,
+ .nid = WEDGE_ANGLE,
+ .mid = 0x95,
+ .req = 8,
+ .direct = EFX_DIR_IN,
+ .def_val = 0x41F00000
+ },
+ { .name = "SVM Level",
+ .parent_nid = MIC_SVM,
+ .nid = SVM_LEVEL,
+ .mid = 0x95,
+ .req = 45,
+ .direct = EFX_DIR_IN,
+ .def_val = 0x3F3D70A4
+ },
+ { .name = "EQ Band0",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_0,
+ .mid = 0x96,
+ .req = 11,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band1",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_1,
+ .mid = 0x96,
+ .req = 12,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band2",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_2,
+ .mid = 0x96,
+ .req = 13,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band3",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_3,
+ .mid = 0x96,
+ .req = 14,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band4",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_4,
+ .mid = 0x96,
+ .req = 15,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band5",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_5,
+ .mid = 0x96,
+ .req = 16,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band6",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_6,
+ .mid = 0x96,
+ .req = 17,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band7",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_7,
+ .mid = 0x96,
+ .req = 18,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band8",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_8,
+ .mid = 0x96,
+ .req = 19,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ },
+ { .name = "EQ Band9",
+ .parent_nid = EQUALIZER,
+ .nid = EQUALIZER_BAND_9,
+ .mid = 0x96,
+ .req = 20,
+ .direct = EFX_DIR_OUT,
+ .def_val = 0x00000000
+ }
+};
+#endif
+
+/* Voice FX Presets */
+#define VOICEFX_MAX_PARAM_COUNT 9
+
+struct ct_voicefx {
+ char *name;
+ hda_nid_t nid;
+ int mid;
+ int reqs[VOICEFX_MAX_PARAM_COUNT]; /*effect module request*/
+};
+
+struct ct_voicefx_preset {
+ char *name; /*preset name*/
+ unsigned int vals[VOICEFX_MAX_PARAM_COUNT];
+};
+
+static const struct ct_voicefx ca0132_voicefx = {
+ .name = "VoiceFX Capture Switch",
+ .nid = VOICEFX,
+ .mid = 0x95,
+ .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18}
+};
+
+static const struct ct_voicefx_preset ca0132_voicefx_presets[] = {
+ { .name = "Neutral",
+ .vals = { 0x00000000, 0x43C80000, 0x44AF0000,
+ 0x44FA0000, 0x3F800000, 0x3F800000,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "Female2Male",
+ .vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
+ 0x44FA0000, 0x3F19999A, 0x3F866666,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "Male2Female",
+ .vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
+ 0x450AC000, 0x4017AE14, 0x3F6B851F,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "ScrappyKid",
+ .vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
+ 0x44FA0000, 0x40400000, 0x3F28F5C3,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "Elderly",
+ .vals = { 0x3F800000, 0x44324000, 0x44BB8000,
+ 0x44E10000, 0x3FB33333, 0x3FB9999A,
+ 0x3F800000, 0x3E3A2E43, 0x00000000 }
+ },
+ { .name = "Orc",
+ .vals = { 0x3F800000, 0x43EA0000, 0x44A52000,
+ 0x45098000, 0x3F266666, 0x3FC00000,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "Elf",
+ .vals = { 0x3F800000, 0x43C70000, 0x44AE6000,
+ 0x45193000, 0x3F8E147B, 0x3F75C28F,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "Dwarf",
+ .vals = { 0x3F800000, 0x43930000, 0x44BEE000,
+ 0x45007000, 0x3F451EB8, 0x3F7851EC,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "AlienBrute",
+ .vals = { 0x3F800000, 0x43BFC5AC, 0x44B28FDF,
+ 0x451F6000, 0x3F266666, 0x3FA7D945,
+ 0x3F800000, 0x3CF5C28F, 0x00000000 }
+ },
+ { .name = "Robot",
+ .vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
+ 0x44FA0000, 0x3FB2718B, 0x3F800000,
+ 0xBC07010E, 0x00000000, 0x00000000 }
+ },
+ { .name = "Marine",
+ .vals = { 0x3F800000, 0x43C20000, 0x44906000,
+ 0x44E70000, 0x3F4CCCCD, 0x3F8A3D71,
+ 0x3F0A3D71, 0x00000000, 0x00000000 }
+ },
+ { .name = "Emo",
+ .vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
+ 0x44FA0000, 0x3F800000, 0x3F800000,
+ 0x3E4CCCCD, 0x00000000, 0x00000000 }
+ },
+ { .name = "DeepVoice",
+ .vals = { 0x3F800000, 0x43A9C5AC, 0x44AA4FDF,
+ 0x44FFC000, 0x3EDBB56F, 0x3F99C4CA,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ },
+ { .name = "Munchkin",
+ .vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
+ 0x44FA0000, 0x3F800000, 0x3F1A043C,
+ 0x3F800000, 0x00000000, 0x00000000 }
+ }
+};
+
+/* ca0132 EQ presets, taken from Windows Sound Blaster Z Driver */
+
+#define EQ_PRESET_MAX_PARAM_COUNT 11
+
+struct ct_eq {
+ char *name;
+ hda_nid_t nid;
+ int mid;
+ int reqs[EQ_PRESET_MAX_PARAM_COUNT]; /*effect module request*/
+};
+
+struct ct_eq_preset {
+ char *name; /*preset name*/
+ unsigned int vals[EQ_PRESET_MAX_PARAM_COUNT];
+};
+
+static const struct ct_eq ca0132_alt_eq_enum = {
+ .name = "FX: Equalizer Preset Switch",
+ .nid = EQ_PRESET_ENUM,
+ .mid = 0x96,
+ .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+};
+
+
+static const struct ct_eq_preset ca0132_alt_eq_presets[] = {
+ { .name = "Flat",
+ .vals = { 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000 }
+ },
+ { .name = "Acoustic",
+ .vals = { 0x00000000, 0x00000000, 0x3F8CCCCD,
+ 0x40000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x40000000,
+ 0x40000000, 0x40000000 }
+ },
+ { .name = "Classical",
+ .vals = { 0x00000000, 0x00000000, 0x40C00000,
+ 0x40C00000, 0x40466666, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000,
+ 0x40466666, 0x40466666 }
+ },
+ { .name = "Country",
+ .vals = { 0x00000000, 0xBF99999A, 0x00000000,
+ 0x3FA66666, 0x3FA66666, 0x3F8CCCCD,
+ 0x00000000, 0x00000000, 0x40000000,
+ 0x40466666, 0x40800000 }
+ },
+ { .name = "Dance",
+ .vals = { 0x00000000, 0xBF99999A, 0x40000000,
+ 0x40466666, 0x40866666, 0xBF99999A,
+ 0xBF99999A, 0x00000000, 0x00000000,
+ 0x40800000, 0x40800000 }
+ },
+ { .name = "Jazz",
+ .vals = { 0x00000000, 0x00000000, 0x00000000,
+ 0x3F8CCCCD, 0x40800000, 0x40800000,
+ 0x40800000, 0x00000000, 0x3F8CCCCD,
+ 0x40466666, 0x40466666 }
+ },
+ { .name = "New Age",
+ .vals = { 0x00000000, 0x00000000, 0x40000000,
+ 0x40000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x3F8CCCCD, 0x40000000,
+ 0x40000000, 0x40000000 }
+ },
+ { .name = "Pop",
+ .vals = { 0x00000000, 0xBFCCCCCD, 0x00000000,
+ 0x40000000, 0x40000000, 0x00000000,
+ 0xBF99999A, 0xBF99999A, 0x00000000,
+ 0x40466666, 0x40C00000 }
+ },
+ { .name = "Rock",
+ .vals = { 0x00000000, 0xBF99999A, 0xBF99999A,
+ 0x3F8CCCCD, 0x40000000, 0xBF99999A,
+ 0xBF99999A, 0x00000000, 0x00000000,
+ 0x40800000, 0x40800000 }
+ },
+ { .name = "Vocal",
+ .vals = { 0x00000000, 0xC0000000, 0xBF99999A,
+ 0xBF99999A, 0x00000000, 0x40466666,
+ 0x40800000, 0x40466666, 0x00000000,
+ 0x00000000, 0x3F8CCCCD }
+ }
+};
+
+/*
+ * DSP reqs for handling full-range speakers/bass redirection. If a speaker is
+ * set as not being full range, and bass redirection is enabled, all
+ * frequencies below the crossover frequency are redirected to the LFE
+ * channel. If the surround configuration has no LFE channel, this can't be
+ * enabled. X-Bass must be disabled when using these.
+ */
+enum speaker_range_reqs {
+ SPEAKER_BASS_REDIRECT = 0x15,
+ SPEAKER_BASS_REDIRECT_XOVER_FREQ = 0x16,
+ /* Between 0x16-0x1a are the X-Bass reqs. */
+ SPEAKER_FULL_RANGE_FRONT_L_R = 0x1a,
+ SPEAKER_FULL_RANGE_CENTER_LFE = 0x1b,
+ SPEAKER_FULL_RANGE_REAR_L_R = 0x1c,
+ SPEAKER_FULL_RANGE_SURROUND_L_R = 0x1d,
+ SPEAKER_BASS_REDIRECT_SUB_GAIN = 0x1e,
+};
+
+/*
+ * Definitions for the DSP req's to handle speaker tuning. These all belong to
+ * module ID 0x96, the output effects module.
+ */
+enum speaker_tuning_reqs {
+ /*
+ * Currently, this value is always set to 0.0f. However, on Windows,
+ * when selecting certain headphone profiles on the new Sound Blaster
+ * connect software, the QUERY_SPEAKER_EQ_ADDRESS req on mid 0x80 is
+ * sent. This gets the speaker EQ address area, which is then used to
+ * send over (presumably) an equalizer profile for the specific
+ * headphone setup. It is sent using the same method the DSP
+ * firmware is uploaded with, which I believe is why the 'ctspeq.bin'
+ * file exists in linux firmware tree but goes unused. It would also
+ * explain why the QUERY_SPEAKER_EQ_ADDRESS req is defined but unused.
+ * Once this profile is sent over, SPEAKER_TUNING_USE_SPEAKER_EQ is
+ * set to 1.0f.
+ */
+ SPEAKER_TUNING_USE_SPEAKER_EQ = 0x1f,
+ SPEAKER_TUNING_ENABLE_CENTER_EQ = 0x20,
+ SPEAKER_TUNING_FRONT_LEFT_VOL_LEVEL = 0x21,
+ SPEAKER_TUNING_FRONT_RIGHT_VOL_LEVEL = 0x22,
+ SPEAKER_TUNING_CENTER_VOL_LEVEL = 0x23,
+ SPEAKER_TUNING_LFE_VOL_LEVEL = 0x24,
+ SPEAKER_TUNING_REAR_LEFT_VOL_LEVEL = 0x25,
+ SPEAKER_TUNING_REAR_RIGHT_VOL_LEVEL = 0x26,
+ SPEAKER_TUNING_SURROUND_LEFT_VOL_LEVEL = 0x27,
+ SPEAKER_TUNING_SURROUND_RIGHT_VOL_LEVEL = 0x28,
+ /*
+ * Inversion is used when setting headphone virtualization to line
+ * out. Not sure why this is, but it's the only place it's ever used.
+ */
+ SPEAKER_TUNING_FRONT_LEFT_INVERT = 0x29,
+ SPEAKER_TUNING_FRONT_RIGHT_INVERT = 0x2a,
+ SPEAKER_TUNING_CENTER_INVERT = 0x2b,
+ SPEAKER_TUNING_LFE_INVERT = 0x2c,
+ SPEAKER_TUNING_REAR_LEFT_INVERT = 0x2d,
+ SPEAKER_TUNING_REAR_RIGHT_INVERT = 0x2e,
+ SPEAKER_TUNING_SURROUND_LEFT_INVERT = 0x2f,
+ SPEAKER_TUNING_SURROUND_RIGHT_INVERT = 0x30,
+ /* Delay is used when setting surround speaker distance in Windows. */
+ SPEAKER_TUNING_FRONT_LEFT_DELAY = 0x31,
+ SPEAKER_TUNING_FRONT_RIGHT_DELAY = 0x32,
+ SPEAKER_TUNING_CENTER_DELAY = 0x33,
+ SPEAKER_TUNING_LFE_DELAY = 0x34,
+ SPEAKER_TUNING_REAR_LEFT_DELAY = 0x35,
+ SPEAKER_TUNING_REAR_RIGHT_DELAY = 0x36,
+ SPEAKER_TUNING_SURROUND_LEFT_DELAY = 0x37,
+ SPEAKER_TUNING_SURROUND_RIGHT_DELAY = 0x38,
+ /* Of these two, only mute seems to ever be used. */
+ SPEAKER_TUNING_MAIN_VOLUME = 0x39,
+ SPEAKER_TUNING_MUTE = 0x3a,
+};
+
+/* Surround output channel count configuration structures. */
+#define SPEAKER_CHANNEL_CFG_COUNT 5
+enum {
+ SPEAKER_CHANNELS_2_0,
+ SPEAKER_CHANNELS_2_1,
+ SPEAKER_CHANNELS_4_0,
+ SPEAKER_CHANNELS_4_1,
+ SPEAKER_CHANNELS_5_1,
+};
+
+struct ca0132_alt_speaker_channel_cfg {
+ char *name;
+ unsigned int val;
+};
+
+static const struct ca0132_alt_speaker_channel_cfg speaker_channel_cfgs[] = {
+ { .name = "2.0",
+ .val = FLOAT_ONE
+ },
+ { .name = "2.1",
+ .val = FLOAT_TWO
+ },
+ { .name = "4.0",
+ .val = FLOAT_FIVE
+ },
+ { .name = "4.1",
+ .val = FLOAT_SIX
+ },
+ { .name = "5.1",
+ .val = FLOAT_EIGHT
+ }
+};
+
+/*
+ * DSP volume setting structs. Req 1 is left volume, req 2 is right volume,
+ * and I don't know what the third req is, but it's always zero. I assume it's
+ * some sort of update or set command to tell the DSP there's new volume info.
+ */
+#define DSP_VOL_OUT 0
+#define DSP_VOL_IN 1
+
+struct ct_dsp_volume_ctl {
+ hda_nid_t vnid;
+ int mid; /* module ID*/
+ unsigned int reqs[3]; /* scp req ID */
+};
+
+static const struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = {
+ { .vnid = VNID_SPK,
+ .mid = 0x32,
+ .reqs = {3, 4, 2}
+ },
+ { .vnid = VNID_MIC,
+ .mid = 0x37,
+ .reqs = {2, 3, 1}
+ }
+};
+
+/* Values for ca0113_mmio_command_set for selecting output. */
+#define AE_CA0113_OUT_SET_COMMANDS 6
+struct ae_ca0113_output_set {
+ unsigned int group[AE_CA0113_OUT_SET_COMMANDS];
+ unsigned int target[AE_CA0113_OUT_SET_COMMANDS];
+ unsigned int vals[NUM_OF_OUTPUTS][AE_CA0113_OUT_SET_COMMANDS];
+};
+
+static const struct ae_ca0113_output_set ae5_ca0113_output_presets = {
+ .group = { 0x30, 0x30, 0x48, 0x48, 0x48, 0x30 },
+ .target = { 0x2e, 0x30, 0x0d, 0x17, 0x19, 0x32 },
+ /* Speakers. */
+ .vals = { { 0x00, 0x00, 0x40, 0x00, 0x00, 0x3f },
+ /* Headphones. */
+ { 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00 } },
+};
+
+static const struct ae_ca0113_output_set ae7_ca0113_output_presets = {
+ .group = { 0x30, 0x30, 0x48, 0x48, 0x48, 0x30 },
+ .target = { 0x2e, 0x30, 0x0d, 0x17, 0x19, 0x32 },
+ /* Speakers. */
+ .vals = { { 0x00, 0x00, 0x40, 0x00, 0x00, 0x3f },
+ /* Headphones. */
+ { 0x3f, 0x3f, 0x00, 0x00, 0x02, 0x00 } },
+};
+
+/* ae5 ca0113 command sequences to set headphone gain levels. */
+#define AE5_HEADPHONE_GAIN_PRESET_MAX_COMMANDS 4
+struct ae5_headphone_gain_set {
+ char *name;
+ unsigned int vals[AE5_HEADPHONE_GAIN_PRESET_MAX_COMMANDS];
+};
+
+static const struct ae5_headphone_gain_set ae5_headphone_gain_presets[] = {
+ { .name = "Low (16-31",
+ .vals = { 0xff, 0x2c, 0xf5, 0x32 }
+ },
+ { .name = "Medium (32-149",
+ .vals = { 0x38, 0xa8, 0x3e, 0x4c }
+ },
+ { .name = "High (150-600",
+ .vals = { 0xff, 0xff, 0xff, 0x7f }
+ }
+};
+
+struct ae5_filter_set {
+ char *name;
+ unsigned int val;
+};
+
+static const struct ae5_filter_set ae5_filter_presets[] = {
+ { .name = "Slow Roll Off",
+ .val = 0xa0
+ },
+ { .name = "Minimum Phase",
+ .val = 0xc0
+ },
+ { .name = "Fast Roll Off",
+ .val = 0x80
+ }
+};
+
+enum hda_cmd_vendor_io {
+ /* for DspIO node */
+ VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000,
+ VENDOR_DSPIO_SCP_WRITE_DATA_HIGH = 0x100,
+
+ VENDOR_DSPIO_STATUS = 0xF01,
+ VENDOR_DSPIO_SCP_POST_READ_DATA = 0x702,
+ VENDOR_DSPIO_SCP_READ_DATA = 0xF02,
+ VENDOR_DSPIO_DSP_INIT = 0x703,
+ VENDOR_DSPIO_SCP_POST_COUNT_QUERY = 0x704,
+ VENDOR_DSPIO_SCP_READ_COUNT = 0xF04,
+
+ /* for ChipIO node */
+ VENDOR_CHIPIO_ADDRESS_LOW = 0x000,
+ VENDOR_CHIPIO_ADDRESS_HIGH = 0x100,
+ VENDOR_CHIPIO_STREAM_FORMAT = 0x200,
+ VENDOR_CHIPIO_DATA_LOW = 0x300,
+ VENDOR_CHIPIO_DATA_HIGH = 0x400,
+
+ VENDOR_CHIPIO_8051_WRITE_DIRECT = 0x500,
+ VENDOR_CHIPIO_8051_READ_DIRECT = 0xD00,
+
+ VENDOR_CHIPIO_GET_PARAMETER = 0xF00,
+ VENDOR_CHIPIO_STATUS = 0xF01,
+ VENDOR_CHIPIO_HIC_POST_READ = 0x702,
+ VENDOR_CHIPIO_HIC_READ_DATA = 0xF03,
+
+ VENDOR_CHIPIO_8051_DATA_WRITE = 0x707,
+ VENDOR_CHIPIO_8051_DATA_READ = 0xF07,
+ VENDOR_CHIPIO_8051_PMEM_READ = 0xF08,
+ VENDOR_CHIPIO_8051_IRAM_WRITE = 0x709,
+ VENDOR_CHIPIO_8051_IRAM_READ = 0xF09,
+
+ VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE = 0x70A,
+ VENDOR_CHIPIO_CT_EXTENSIONS_GET = 0xF0A,
+
+ VENDOR_CHIPIO_PLL_PMU_WRITE = 0x70C,
+ VENDOR_CHIPIO_PLL_PMU_READ = 0xF0C,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW = 0x70D,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH = 0x70E,
+ VENDOR_CHIPIO_FLAG_SET = 0x70F,
+ VENDOR_CHIPIO_FLAGS_GET = 0xF0F,
+ VENDOR_CHIPIO_PARAM_SET = 0x710,
+ VENDOR_CHIPIO_PARAM_GET = 0xF10,
+
+ VENDOR_CHIPIO_PORT_ALLOC_CONFIG_SET = 0x711,
+ VENDOR_CHIPIO_PORT_ALLOC_SET = 0x712,
+ VENDOR_CHIPIO_PORT_ALLOC_GET = 0xF12,
+ VENDOR_CHIPIO_PORT_FREE_SET = 0x713,
+
+ VENDOR_CHIPIO_PARAM_EX_ID_GET = 0xF17,
+ VENDOR_CHIPIO_PARAM_EX_ID_SET = 0x717,
+ VENDOR_CHIPIO_PARAM_EX_VALUE_GET = 0xF18,
+ VENDOR_CHIPIO_PARAM_EX_VALUE_SET = 0x718,
+
+ VENDOR_CHIPIO_DMIC_CTL_SET = 0x788,
+ VENDOR_CHIPIO_DMIC_CTL_GET = 0xF88,
+ VENDOR_CHIPIO_DMIC_PIN_SET = 0x789,
+ VENDOR_CHIPIO_DMIC_PIN_GET = 0xF89,
+ VENDOR_CHIPIO_DMIC_MCLK_SET = 0x78A,
+ VENDOR_CHIPIO_DMIC_MCLK_GET = 0xF8A,
+
+ VENDOR_CHIPIO_EAPD_SEL_SET = 0x78D
+};
+
+/*
+ * Control flag IDs
+ */
+enum control_flag_id {
+ /* Connection manager stream setup is bypassed/enabled */
+ CONTROL_FLAG_C_MGR = 0,
+ /* DSP DMA is bypassed/enabled */
+ CONTROL_FLAG_DMA = 1,
+ /* 8051 'idle' mode is disabled/enabled */
+ CONTROL_FLAG_IDLE_ENABLE = 2,
+ /* Tracker for the SPDIF-in path is bypassed/enabled */
+ CONTROL_FLAG_TRACKER = 3,
+ /* DigitalOut to Spdif2Out connection is disabled/enabled */
+ CONTROL_FLAG_SPDIF2OUT = 4,
+ /* Digital Microphone is disabled/enabled */
+ CONTROL_FLAG_DMIC = 5,
+ /* ADC_B rate is 48 kHz/96 kHz */
+ CONTROL_FLAG_ADC_B_96KHZ = 6,
+ /* ADC_C rate is 48 kHz/96 kHz */
+ CONTROL_FLAG_ADC_C_96KHZ = 7,
+ /* DAC rate is 48 kHz/96 kHz (affects all DACs) */
+ CONTROL_FLAG_DAC_96KHZ = 8,
+ /* DSP rate is 48 kHz/96 kHz */
+ CONTROL_FLAG_DSP_96KHZ = 9,
+ /* SRC clock is 98 MHz/196 MHz (196 MHz forces rate to 96 KHz) */
+ CONTROL_FLAG_SRC_CLOCK_196MHZ = 10,
+ /* SRC rate is 48 kHz/96 kHz (48 kHz disabled when clock is 196 MHz) */
+ CONTROL_FLAG_SRC_RATE_96KHZ = 11,
+ /* Decode Loop (DSP->SRC->DSP) is disabled/enabled */
+ CONTROL_FLAG_DECODE_LOOP = 12,
+ /* De-emphasis filter on DAC-1 disabled/enabled */
+ CONTROL_FLAG_DAC1_DEEMPHASIS = 13,
+ /* De-emphasis filter on DAC-2 disabled/enabled */
+ CONTROL_FLAG_DAC2_DEEMPHASIS = 14,
+ /* De-emphasis filter on DAC-3 disabled/enabled */
+ CONTROL_FLAG_DAC3_DEEMPHASIS = 15,
+ /* High-pass filter on ADC_B disabled/enabled */
+ CONTROL_FLAG_ADC_B_HIGH_PASS = 16,
+ /* High-pass filter on ADC_C disabled/enabled */
+ CONTROL_FLAG_ADC_C_HIGH_PASS = 17,
+ /* Common mode on Port_A disabled/enabled */
+ CONTROL_FLAG_PORT_A_COMMON_MODE = 18,
+ /* Common mode on Port_D disabled/enabled */
+ CONTROL_FLAG_PORT_D_COMMON_MODE = 19,
+ /* Impedance for ramp generator on Port_A 16 Ohm/10K Ohm */
+ CONTROL_FLAG_PORT_A_10KOHM_LOAD = 20,
+ /* Impedance for ramp generator on Port_D, 16 Ohm/10K Ohm */
+ CONTROL_FLAG_PORT_D_10KOHM_LOAD = 21,
+ /* ASI rate is 48kHz/96kHz */
+ CONTROL_FLAG_ASI_96KHZ = 22,
+ /* DAC power settings able to control attached ports no/yes */
+ CONTROL_FLAG_DACS_CONTROL_PORTS = 23,
+ /* Clock Stop OK reporting is disabled/enabled */
+ CONTROL_FLAG_CONTROL_STOP_OK_ENABLE = 24,
+ /* Number of control flags */
+ CONTROL_FLAGS_MAX = (CONTROL_FLAG_CONTROL_STOP_OK_ENABLE+1)
+};
+
+/*
+ * Control parameter IDs
+ */
+enum control_param_id {
+ /* 0: None, 1: Mic1In*/
+ CONTROL_PARAM_VIP_SOURCE = 1,
+ /* 0: force HDA, 1: allow DSP if HDA Spdif1Out stream is idle */
+ CONTROL_PARAM_SPDIF1_SOURCE = 2,
+ /* Port A output stage gain setting to use when 16 Ohm output
+ * impedance is selected*/
+ CONTROL_PARAM_PORTA_160OHM_GAIN = 8,
+ /* Port D output stage gain setting to use when 16 Ohm output
+ * impedance is selected*/
+ CONTROL_PARAM_PORTD_160OHM_GAIN = 10,
+
+ /*
+ * This control param name was found in the 8051 memory, and makes
+ * sense given the fact the AE-5 uses it and has the ASI flag set.
+ */
+ CONTROL_PARAM_ASI = 23,
+
+ /* Stream Control */
+
+ /* Select stream with the given ID */
+ CONTROL_PARAM_STREAM_ID = 24,
+ /* Source connection point for the selected stream */
+ CONTROL_PARAM_STREAM_SOURCE_CONN_POINT = 25,
+ /* Destination connection point for the selected stream */
+ CONTROL_PARAM_STREAM_DEST_CONN_POINT = 26,
+ /* Number of audio channels in the selected stream */
+ CONTROL_PARAM_STREAMS_CHANNELS = 27,
+ /*Enable control for the selected stream */
+ CONTROL_PARAM_STREAM_CONTROL = 28,
+
+ /* Connection Point Control */
+
+ /* Select connection point with the given ID */
+ CONTROL_PARAM_CONN_POINT_ID = 29,
+ /* Connection point sample rate */
+ CONTROL_PARAM_CONN_POINT_SAMPLE_RATE = 30,
+
+ /* Node Control */
+
+ /* Select HDA node with the given ID */
+ CONTROL_PARAM_NODE_ID = 31
+};
+
+/*
+ * Dsp Io Status codes
+ */
+enum hda_vendor_status_dspio {
+ /* Success */
+ VENDOR_STATUS_DSPIO_OK = 0x00,
+ /* Busy, unable to accept new command, the host must retry */
+ VENDOR_STATUS_DSPIO_BUSY = 0x01,
+ /* SCP command queue is full */
+ VENDOR_STATUS_DSPIO_SCP_COMMAND_QUEUE_FULL = 0x02,
+ /* SCP response queue is empty */
+ VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY = 0x03
+};
+
+/*
+ * Chip Io Status codes
+ */
+enum hda_vendor_status_chipio {
+ /* Success */
+ VENDOR_STATUS_CHIPIO_OK = 0x00,
+ /* Busy, unable to accept new command, the host must retry */
+ VENDOR_STATUS_CHIPIO_BUSY = 0x01
+};
+
+/*
+ * CA0132 sample rate
+ */
+enum ca0132_sample_rate {
+ SR_6_000 = 0x00,
+ SR_8_000 = 0x01,
+ SR_9_600 = 0x02,
+ SR_11_025 = 0x03,
+ SR_16_000 = 0x04,
+ SR_22_050 = 0x05,
+ SR_24_000 = 0x06,
+ SR_32_000 = 0x07,
+ SR_44_100 = 0x08,
+ SR_48_000 = 0x09,
+ SR_88_200 = 0x0A,
+ SR_96_000 = 0x0B,
+ SR_144_000 = 0x0C,
+ SR_176_400 = 0x0D,
+ SR_192_000 = 0x0E,
+ SR_384_000 = 0x0F,
+
+ SR_COUNT = 0x10,
+
+ SR_RATE_UNKNOWN = 0x1F
+};
+
+enum dsp_download_state {
+ DSP_DOWNLOAD_FAILED = -1,
+ DSP_DOWNLOAD_INIT = 0,
+ DSP_DOWNLOADING = 1,
+ DSP_DOWNLOADED = 2
+};
+
+/* retrieve parameters from hda format */
+#define get_hdafmt_chs(fmt) (fmt & 0xf)
+#define get_hdafmt_bits(fmt) ((fmt >> 4) & 0x7)
+#define get_hdafmt_rate(fmt) ((fmt >> 8) & 0x7f)
+#define get_hdafmt_type(fmt) ((fmt >> 15) & 0x1)
+
+/*
+ * CA0132 specific
+ */
+
+struct ca0132_spec {
+ const struct snd_kcontrol_new *mixers[5];
+ unsigned int num_mixers;
+ const struct hda_verb *base_init_verbs;
+ const struct hda_verb *base_exit_verbs;
+ const struct hda_verb *chip_init_verbs;
+ const struct hda_verb *desktop_init_verbs;
+ struct hda_verb *spec_init_verbs;
+ struct auto_pin_cfg autocfg;
+
+ /* Nodes configurations */
+ struct hda_multi_out multiout;
+ hda_nid_t out_pins[AUTO_CFG_MAX_OUTS];
+ hda_nid_t dacs[AUTO_CFG_MAX_OUTS];
+ unsigned int num_outputs;
+ hda_nid_t input_pins[AUTO_PIN_LAST];
+ hda_nid_t adcs[AUTO_PIN_LAST];
+ hda_nid_t dig_out;
+ hda_nid_t dig_in;
+ unsigned int num_inputs;
+ hda_nid_t shared_mic_nid;
+ hda_nid_t shared_out_nid;
+ hda_nid_t unsol_tag_hp;
+ hda_nid_t unsol_tag_front_hp; /* for desktop ca0132 codecs */
+ hda_nid_t unsol_tag_amic1;
+
+ /* chip access */
+ struct mutex chipio_mutex; /* chip access mutex */
+ u32 curr_chip_addx;
+
+ /* DSP download related */
+ enum dsp_download_state dsp_state;
+ unsigned int dsp_stream_id;
+ unsigned int wait_scp;
+ unsigned int wait_scp_header;
+ unsigned int wait_num_data;
+ unsigned int scp_resp_header;
+ unsigned int scp_resp_data[4];
+ unsigned int scp_resp_count;
+ bool startup_check_entered;
+ bool dsp_reload;
+
+ /* mixer and effects related */
+ unsigned char dmic_ctl;
+ int cur_out_type;
+ int cur_mic_type;
+ long vnode_lvol[VNODES_COUNT];
+ long vnode_rvol[VNODES_COUNT];
+ long vnode_lswitch[VNODES_COUNT];
+ long vnode_rswitch[VNODES_COUNT];
+ long effects_switch[EFFECTS_COUNT];
+ long voicefx_val;
+ long cur_mic_boost;
+ /* ca0132_alt control related values */
+ unsigned char in_enum_val;
+ unsigned char out_enum_val;
+ unsigned char channel_cfg_val;
+ unsigned char speaker_range_val[2];
+ unsigned char mic_boost_enum_val;
+ unsigned char smart_volume_setting;
+ unsigned char bass_redirection_val;
+ long bass_redirect_xover_freq;
+ long fx_ctl_val[EFFECT_LEVEL_SLIDERS];
+ long xbass_xover_freq;
+ long eq_preset_val;
+ unsigned int tlv[4];
+ struct hda_vmaster_mute_hook vmaster_mute;
+ /* AE-5 Control values */
+ unsigned char ae5_headphone_gain_val;
+ unsigned char ae5_filter_val;
+ /* ZxR Control Values */
+ unsigned char zxr_gain_set;
+
+ struct hda_codec *codec;
+ struct delayed_work unsol_hp_work;
+ int quirk;
+
+#ifdef ENABLE_TUNING_CONTROLS
+ long cur_ctl_vals[TUNING_CTLS_COUNT];
+#endif
+ /*
+ * The Recon3D, Sound Blaster Z, Sound Blaster ZxR, and Sound Blaster
+ * AE-5 all use PCI region 2 to toggle GPIO and other currently unknown
+ * things.
+ */
+ bool use_pci_mmio;
+ void __iomem *mem_base;
+
+ /*
+ * Whether or not to use the alt functions like alt_select_out,
+ * alt_select_in, etc. Only used on desktop codecs for now, because of
+ * surround sound support.
+ */
+ bool use_alt_functions;
+
+ /*
+ * Whether or not to use alt controls: volume effect sliders, EQ
+ * presets, smart volume presets, and new control names with FX prefix.
+ * Renames PlayEnhancement and CrystalVoice too.
+ */
+ bool use_alt_controls;
+};
+
+/*
+ * CA0132 quirks table
+ */
+enum {
+ QUIRK_NONE,
+ QUIRK_ALIENWARE,
+ QUIRK_ALIENWARE_M17XR4,
+ QUIRK_SBZ,
+ QUIRK_ZXR,
+ QUIRK_ZXR_DBPRO,
+ QUIRK_R3DI,
+ QUIRK_R3D,
+ QUIRK_AE5,
+ QUIRK_AE7,
+};
+
+#ifdef CONFIG_PCI
+#define ca0132_quirk(spec) ((spec)->quirk)
+#define ca0132_use_pci_mmio(spec) ((spec)->use_pci_mmio)
+#define ca0132_use_alt_functions(spec) ((spec)->use_alt_functions)
+#define ca0132_use_alt_controls(spec) ((spec)->use_alt_controls)
+#else
+#define ca0132_quirk(spec) ({ (void)(spec); QUIRK_NONE; })
+#define ca0132_use_alt_functions(spec) ({ (void)(spec); false; })
+#define ca0132_use_pci_mmio(spec) ({ (void)(spec); false; })
+#define ca0132_use_alt_controls(spec) ({ (void)(spec); false; })
+#endif
+
+static const struct hda_pintbl alienware_pincfgs[] = {
+ { 0x0b, 0x90170110 }, /* Builtin Speaker */
+ { 0x0c, 0x411111f0 }, /* N/A */
+ { 0x0d, 0x411111f0 }, /* N/A */
+ { 0x0e, 0x411111f0 }, /* N/A */
+ { 0x0f, 0x0321101f }, /* HP */
+ { 0x10, 0x411111f0 }, /* Headset? disabled for now */
+ { 0x11, 0x03a11021 }, /* Mic */
+ { 0x12, 0xd5a30140 }, /* Builtin Mic */
+ { 0x13, 0x411111f0 }, /* N/A */
+ { 0x18, 0x411111f0 }, /* N/A */
+ {}
+};
+
+/* Sound Blaster Z pin configs taken from Windows Driver */
+static const struct hda_pintbl sbz_pincfgs[] = {
+ { 0x0b, 0x01017010 }, /* Port G -- Lineout FRONT L/R */
+ { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */
+ { 0x0d, 0x014510f0 }, /* Digital Out */
+ { 0x0e, 0x01c510f0 }, /* SPDIF In */
+ { 0x0f, 0x0221701f }, /* Port A -- BackPanel HP */
+ { 0x10, 0x01017012 }, /* Port D -- Center/LFE or FP Hp */
+ { 0x11, 0x01017014 }, /* Port B -- LineMicIn2 / Rear L/R */
+ { 0x12, 0x01a170f0 }, /* Port C -- LineIn1 */
+ { 0x13, 0x908700f0 }, /* What U Hear In*/
+ { 0x18, 0x50d000f0 }, /* N/A */
+ {}
+};
+
+/* Sound Blaster ZxR pin configs taken from Windows Driver */
+static const struct hda_pintbl zxr_pincfgs[] = {
+ { 0x0b, 0x01047110 }, /* Port G -- Lineout FRONT L/R */
+ { 0x0c, 0x414510f0 }, /* SPDIF Out 1 - Disabled*/
+ { 0x0d, 0x014510f0 }, /* Digital Out */
+ { 0x0e, 0x41c520f0 }, /* SPDIF In - Disabled*/
+ { 0x0f, 0x0122711f }, /* Port A -- BackPanel HP */
+ { 0x10, 0x01017111 }, /* Port D -- Center/LFE */
+ { 0x11, 0x01017114 }, /* Port B -- LineMicIn2 / Rear L/R */
+ { 0x12, 0x01a271f0 }, /* Port C -- LineIn1 */
+ { 0x13, 0x908700f0 }, /* What U Hear In*/
+ { 0x18, 0x50d000f0 }, /* N/A */
+ {}
+};
+
+/* Recon3D pin configs taken from Windows Driver */
+static const struct hda_pintbl r3d_pincfgs[] = {
+ { 0x0b, 0x01014110 }, /* Port G -- Lineout FRONT L/R */
+ { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */
+ { 0x0d, 0x014510f0 }, /* Digital Out */
+ { 0x0e, 0x01c520f0 }, /* SPDIF In */
+ { 0x0f, 0x0221401f }, /* Port A -- BackPanel HP */
+ { 0x10, 0x01016011 }, /* Port D -- Center/LFE or FP Hp */
+ { 0x11, 0x01011014 }, /* Port B -- LineMicIn2 / Rear L/R */
+ { 0x12, 0x02a090f0 }, /* Port C -- LineIn1 */
+ { 0x13, 0x908700f0 }, /* What U Hear In*/
+ { 0x18, 0x50d000f0 }, /* N/A */
+ {}
+};
+
+/* Sound Blaster AE-5 pin configs taken from Windows Driver */
+static const struct hda_pintbl ae5_pincfgs[] = {
+ { 0x0b, 0x01017010 }, /* Port G -- Lineout FRONT L/R */
+ { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */
+ { 0x0d, 0x014510f0 }, /* Digital Out */
+ { 0x0e, 0x01c510f0 }, /* SPDIF In */
+ { 0x0f, 0x01017114 }, /* Port A -- Rear L/R. */
+ { 0x10, 0x01017012 }, /* Port D -- Center/LFE or FP Hp */
+ { 0x11, 0x012170ff }, /* Port B -- LineMicIn2 / Rear Headphone */
+ { 0x12, 0x01a170f0 }, /* Port C -- LineIn1 */
+ { 0x13, 0x908700f0 }, /* What U Hear In*/
+ { 0x18, 0x50d000f0 }, /* N/A */
+ {}
+};
+
+/* Recon3D integrated pin configs taken from Windows Driver */
+static const struct hda_pintbl r3di_pincfgs[] = {
+ { 0x0b, 0x01014110 }, /* Port G -- Lineout FRONT L/R */
+ { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */
+ { 0x0d, 0x014510f0 }, /* Digital Out */
+ { 0x0e, 0x41c520f0 }, /* SPDIF In */
+ { 0x0f, 0x0221401f }, /* Port A -- BackPanel HP */
+ { 0x10, 0x01016011 }, /* Port D -- Center/LFE or FP Hp */
+ { 0x11, 0x01011014 }, /* Port B -- LineMicIn2 / Rear L/R */
+ { 0x12, 0x02a090f0 }, /* Port C -- LineIn1 */
+ { 0x13, 0x908700f0 }, /* What U Hear In*/
+ { 0x18, 0x500000f0 }, /* N/A */
+ {}
+};
+
+static const struct hda_pintbl ae7_pincfgs[] = {
+ { 0x0b, 0x01017010 },
+ { 0x0c, 0x014510f0 },
+ { 0x0d, 0x414510f0 },
+ { 0x0e, 0x01c520f0 },
+ { 0x0f, 0x01017114 },
+ { 0x10, 0x01017011 },
+ { 0x11, 0x018170ff },
+ { 0x12, 0x01a170f0 },
+ { 0x13, 0x908700f0 },
+ { 0x18, 0x500000f0 },
+ {}
+};
+
+static const struct snd_pci_quirk ca0132_quirks[] = {
+ SND_PCI_QUIRK(0x1028, 0x057b, "Alienware M17x R4", QUIRK_ALIENWARE_M17XR4),
+ SND_PCI_QUIRK(0x1028, 0x0685, "Alienware 15 2015", QUIRK_ALIENWARE),
+ SND_PCI_QUIRK(0x1028, 0x0688, "Alienware 17 2015", QUIRK_ALIENWARE),
+ SND_PCI_QUIRK(0x1028, 0x0708, "Alienware 15 R2 2016", QUIRK_ALIENWARE),
+ SND_PCI_QUIRK(0x1102, 0x0010, "Sound Blaster Z", QUIRK_SBZ),
+ SND_PCI_QUIRK(0x1102, 0x0023, "Sound Blaster Z", QUIRK_SBZ),
+ SND_PCI_QUIRK(0x1102, 0x0027, "Sound Blaster Z", QUIRK_SBZ),
+ SND_PCI_QUIRK(0x1102, 0x0033, "Sound Blaster ZxR", QUIRK_SBZ),
+ SND_PCI_QUIRK(0x1458, 0xA016, "Recon3Di", QUIRK_R3DI),
+ SND_PCI_QUIRK(0x1458, 0xA026, "Gigabyte G1.Sniper Z97", QUIRK_R3DI),
+ SND_PCI_QUIRK(0x1458, 0xA036, "Gigabyte GA-Z170X-Gaming 7", QUIRK_R3DI),
+ SND_PCI_QUIRK(0x3842, 0x1038, "EVGA X99 Classified", QUIRK_R3DI),
+ SND_PCI_QUIRK(0x3842, 0x104b, "EVGA X299 Dark", QUIRK_R3DI),
+ SND_PCI_QUIRK(0x3842, 0x1055, "EVGA Z390 DARK", QUIRK_R3DI),
+ SND_PCI_QUIRK(0x1102, 0x0013, "Recon3D", QUIRK_R3D),
+ SND_PCI_QUIRK(0x1102, 0x0018, "Recon3D", QUIRK_R3D),
+ SND_PCI_QUIRK(0x1102, 0x0051, "Sound Blaster AE-5", QUIRK_AE5),
+ SND_PCI_QUIRK(0x1102, 0x0191, "Sound Blaster AE-5 Plus", QUIRK_AE5),
+ SND_PCI_QUIRK(0x1102, 0x0081, "Sound Blaster AE-7", QUIRK_AE7),
+ {}
+};
+
+/* Output selection quirk info structures. */
+#define MAX_QUIRK_MMIO_GPIO_SET_VALS 3
+#define MAX_QUIRK_SCP_SET_VALS 2
+struct ca0132_alt_out_set_info {
+ unsigned int dac2port; /* ParamID 0x0d value. */
+
+ bool has_hda_gpio;
+ char hda_gpio_pin;
+ char hda_gpio_set;
+
+ unsigned int mmio_gpio_count;
+ char mmio_gpio_pin[MAX_QUIRK_MMIO_GPIO_SET_VALS];
+ char mmio_gpio_set[MAX_QUIRK_MMIO_GPIO_SET_VALS];
+
+ unsigned int scp_cmds_count;
+ unsigned int scp_cmd_mid[MAX_QUIRK_SCP_SET_VALS];
+ unsigned int scp_cmd_req[MAX_QUIRK_SCP_SET_VALS];
+ unsigned int scp_cmd_val[MAX_QUIRK_SCP_SET_VALS];
+
+ bool has_chipio_write;
+ unsigned int chipio_write_addr;
+ unsigned int chipio_write_data;
+};
+
+struct ca0132_alt_out_set_quirk_data {
+ int quirk_id;
+
+ bool has_headphone_gain;
+ bool is_ae_series;
+
+ struct ca0132_alt_out_set_info out_set_info[NUM_OF_OUTPUTS];
+};
+
+static const struct ca0132_alt_out_set_quirk_data quirk_out_set_data[] = {
+ { .quirk_id = QUIRK_R3DI,
+ .has_headphone_gain = false,
+ .is_ae_series = false,
+ .out_set_info = {
+ /* Speakers. */
+ { .dac2port = 0x24,
+ .has_hda_gpio = true,
+ .hda_gpio_pin = 2,
+ .hda_gpio_set = 1,
+ .mmio_gpio_count = 0,
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ },
+ /* Headphones. */
+ { .dac2port = 0x21,
+ .has_hda_gpio = true,
+ .hda_gpio_pin = 2,
+ .hda_gpio_set = 0,
+ .mmio_gpio_count = 0,
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ } },
+ },
+ { .quirk_id = QUIRK_R3D,
+ .has_headphone_gain = false,
+ .is_ae_series = false,
+ .out_set_info = {
+ /* Speakers. */
+ { .dac2port = 0x24,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 1,
+ .mmio_gpio_pin = { 1 },
+ .mmio_gpio_set = { 1 },
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ },
+ /* Headphones. */
+ { .dac2port = 0x21,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 1,
+ .mmio_gpio_pin = { 1 },
+ .mmio_gpio_set = { 0 },
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ } },
+ },
+ { .quirk_id = QUIRK_SBZ,
+ .has_headphone_gain = false,
+ .is_ae_series = false,
+ .out_set_info = {
+ /* Speakers. */
+ { .dac2port = 0x18,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 3,
+ .mmio_gpio_pin = { 7, 4, 1 },
+ .mmio_gpio_set = { 0, 1, 1 },
+ .scp_cmds_count = 0,
+ .has_chipio_write = false, },
+ /* Headphones. */
+ { .dac2port = 0x12,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 3,
+ .mmio_gpio_pin = { 7, 4, 1 },
+ .mmio_gpio_set = { 1, 1, 0 },
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ } },
+ },
+ { .quirk_id = QUIRK_ZXR,
+ .has_headphone_gain = true,
+ .is_ae_series = false,
+ .out_set_info = {
+ /* Speakers. */
+ { .dac2port = 0x24,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 3,
+ .mmio_gpio_pin = { 2, 3, 5 },
+ .mmio_gpio_set = { 1, 1, 0 },
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ },
+ /* Headphones. */
+ { .dac2port = 0x21,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 3,
+ .mmio_gpio_pin = { 2, 3, 5 },
+ .mmio_gpio_set = { 0, 1, 1 },
+ .scp_cmds_count = 0,
+ .has_chipio_write = false,
+ } },
+ },
+ { .quirk_id = QUIRK_AE5,
+ .has_headphone_gain = true,
+ .is_ae_series = true,
+ .out_set_info = {
+ /* Speakers. */
+ { .dac2port = 0xa4,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 0,
+ .scp_cmds_count = 2,
+ .scp_cmd_mid = { 0x96, 0x96 },
+ .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT,
+ SPEAKER_TUNING_FRONT_RIGHT_INVERT },
+ .scp_cmd_val = { FLOAT_ZERO, FLOAT_ZERO },
+ .has_chipio_write = true,
+ .chipio_write_addr = 0x0018b03c,
+ .chipio_write_data = 0x00000012
+ },
+ /* Headphones. */
+ { .dac2port = 0xa1,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 0,
+ .scp_cmds_count = 2,
+ .scp_cmd_mid = { 0x96, 0x96 },
+ .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT,
+ SPEAKER_TUNING_FRONT_RIGHT_INVERT },
+ .scp_cmd_val = { FLOAT_ONE, FLOAT_ONE },
+ .has_chipio_write = true,
+ .chipio_write_addr = 0x0018b03c,
+ .chipio_write_data = 0x00000012
+ } },
+ },
+ { .quirk_id = QUIRK_AE7,
+ .has_headphone_gain = true,
+ .is_ae_series = true,
+ .out_set_info = {
+ /* Speakers. */
+ { .dac2port = 0x58,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 1,
+ .mmio_gpio_pin = { 0 },
+ .mmio_gpio_set = { 1 },
+ .scp_cmds_count = 2,
+ .scp_cmd_mid = { 0x96, 0x96 },
+ .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT,
+ SPEAKER_TUNING_FRONT_RIGHT_INVERT },
+ .scp_cmd_val = { FLOAT_ZERO, FLOAT_ZERO },
+ .has_chipio_write = true,
+ .chipio_write_addr = 0x0018b03c,
+ .chipio_write_data = 0x00000000
+ },
+ /* Headphones. */
+ { .dac2port = 0x58,
+ .has_hda_gpio = false,
+ .mmio_gpio_count = 1,
+ .mmio_gpio_pin = { 0 },
+ .mmio_gpio_set = { 1 },
+ .scp_cmds_count = 2,
+ .scp_cmd_mid = { 0x96, 0x96 },
+ .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT,
+ SPEAKER_TUNING_FRONT_RIGHT_INVERT },
+ .scp_cmd_val = { FLOAT_ONE, FLOAT_ONE },
+ .has_chipio_write = true,
+ .chipio_write_addr = 0x0018b03c,
+ .chipio_write_data = 0x00000010
+ } },
+ }
+};
+
+/*
+ * CA0132 codec access
+ */
+static unsigned int codec_send_command(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int verb, unsigned int parm, unsigned int *res)
+{
+ unsigned int response;
+ response = snd_hda_codec_read(codec, nid, 0, verb, parm);
+ *res = response;
+
+ return ((response == -1) ? -1 : 0);
+}
+
+static int codec_set_converter_format(struct hda_codec *codec, hda_nid_t nid,
+ unsigned short converter_format, unsigned int *res)
+{
+ return codec_send_command(codec, nid, VENDOR_CHIPIO_STREAM_FORMAT,
+ converter_format & 0xffff, res);
+}
+
+static int codec_set_converter_stream_channel(struct hda_codec *codec,
+ hda_nid_t nid, unsigned char stream,
+ unsigned char channel, unsigned int *res)
+{
+ unsigned char converter_stream_channel = 0;
+
+ converter_stream_channel = (stream << 4) | (channel & 0x0f);
+ return codec_send_command(codec, nid, AC_VERB_SET_CHANNEL_STREAMID,
+ converter_stream_channel, res);
+}
+
+/* Chip access helper function */
+static int chipio_send(struct hda_codec *codec,
+ unsigned int reg,
+ unsigned int data)
+{
+ unsigned int res;
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+
+ /* send bits of data specified by reg */
+ do {
+ res = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0,
+ reg, data);
+ if (res == VENDOR_STATUS_CHIPIO_OK)
+ return 0;
+ msleep(20);
+ } while (time_before(jiffies, timeout));
+
+ return -EIO;
+}
+
+/*
+ * Write chip address through the vendor widget -- NOT protected by the Mutex!
+ */
+static int chipio_write_address(struct hda_codec *codec,
+ unsigned int chip_addx)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int res;
+
+ if (spec->curr_chip_addx == chip_addx)
+ return 0;
+
+ /* send low 16 bits of the address */
+ res = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_LOW,
+ chip_addx & 0xffff);
+
+ if (res != -EIO) {
+ /* send high 16 bits of the address */
+ res = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_HIGH,
+ chip_addx >> 16);
+ }
+
+ spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx;
+
+ return res;
+}
+
+/*
+ * Write data through the vendor widget -- NOT protected by the Mutex!
+ */
+static int chipio_write_data(struct hda_codec *codec, unsigned int data)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int res;
+
+ /* send low 16 bits of the data */
+ res = chipio_send(codec, VENDOR_CHIPIO_DATA_LOW, data & 0xffff);
+
+ if (res != -EIO) {
+ /* send high 16 bits of the data */
+ res = chipio_send(codec, VENDOR_CHIPIO_DATA_HIGH,
+ data >> 16);
+ }
+
+ /*If no error encountered, automatically increment the address
+ as per chip behaviour*/
+ spec->curr_chip_addx = (res != -EIO) ?
+ (spec->curr_chip_addx + 4) : ~0U;
+ return res;
+}
+
+/*
+ * Write multiple data through the vendor widget -- NOT protected by the Mutex!
+ */
+static int chipio_write_data_multiple(struct hda_codec *codec,
+ const u32 *data,
+ unsigned int count)
+{
+ int status = 0;
+
+ if (data == NULL) {
+ codec_dbg(codec, "chipio_write_data null ptr\n");
+ return -EINVAL;
+ }
+
+ while ((count-- != 0) && (status == 0))
+ status = chipio_write_data(codec, *data++);
+
+ return status;
+}
+
+
+/*
+ * Read data through the vendor widget -- NOT protected by the Mutex!
+ */
+static int chipio_read_data(struct hda_codec *codec, unsigned int *data)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int res;
+
+ /* post read */
+ res = chipio_send(codec, VENDOR_CHIPIO_HIC_POST_READ, 0);
+
+ if (res != -EIO) {
+ /* read status */
+ res = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0);
+ }
+
+ if (res != -EIO) {
+ /* read data */
+ *data = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_HIC_READ_DATA,
+ 0);
+ }
+
+ /*If no error encountered, automatically increment the address
+ as per chip behaviour*/
+ spec->curr_chip_addx = (res != -EIO) ?
+ (spec->curr_chip_addx + 4) : ~0U;
+ return res;
+}
+
+/*
+ * Write given value to the given address through the chip I/O widget.
+ * protected by the Mutex
+ */
+static int chipio_write(struct hda_codec *codec,
+ unsigned int chip_addx, const unsigned int data)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int err;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ /* write the address, and if successful proceed to write data */
+ err = chipio_write_address(codec, chip_addx);
+ if (err < 0)
+ goto exit;
+
+ err = chipio_write_data(codec, data);
+ if (err < 0)
+ goto exit;
+
+exit:
+ mutex_unlock(&spec->chipio_mutex);
+ return err;
+}
+
+/*
+ * Write given value to the given address through the chip I/O widget.
+ * not protected by the Mutex
+ */
+static int chipio_write_no_mutex(struct hda_codec *codec,
+ unsigned int chip_addx, const unsigned int data)
+{
+ int err;
+
+
+ /* write the address, and if successful proceed to write data */
+ err = chipio_write_address(codec, chip_addx);
+ if (err < 0)
+ goto exit;
+
+ err = chipio_write_data(codec, data);
+ if (err < 0)
+ goto exit;
+
+exit:
+ return err;
+}
+
+/*
+ * Write multiple values to the given address through the chip I/O widget.
+ * protected by the Mutex
+ */
+static int chipio_write_multiple(struct hda_codec *codec,
+ u32 chip_addx,
+ const u32 *data,
+ unsigned int count)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int status;
+
+ mutex_lock(&spec->chipio_mutex);
+ status = chipio_write_address(codec, chip_addx);
+ if (status < 0)
+ goto error;
+
+ status = chipio_write_data_multiple(codec, data, count);
+error:
+ mutex_unlock(&spec->chipio_mutex);
+
+ return status;
+}
+
+/*
+ * Read the given address through the chip I/O widget
+ * protected by the Mutex
+ */
+static int chipio_read(struct hda_codec *codec,
+ unsigned int chip_addx, unsigned int *data)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int err;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ /* write the address, and if successful proceed to write data */
+ err = chipio_write_address(codec, chip_addx);
+ if (err < 0)
+ goto exit;
+
+ err = chipio_read_data(codec, data);
+ if (err < 0)
+ goto exit;
+
+exit:
+ mutex_unlock(&spec->chipio_mutex);
+ return err;
+}
+
+/*
+ * Set chip control flags through the chip I/O widget.
+ */
+static void chipio_set_control_flag(struct hda_codec *codec,
+ enum control_flag_id flag_id,
+ bool flag_state)
+{
+ unsigned int val;
+ unsigned int flag_bit;
+
+ flag_bit = (flag_state ? 1 : 0);
+ val = (flag_bit << 7) | (flag_id);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_FLAG_SET, val);
+}
+
+/*
+ * Set chip parameters through the chip I/O widget.
+ */
+static void chipio_set_control_param(struct hda_codec *codec,
+ enum control_param_id param_id, int param_val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int val;
+
+ if ((param_id < 32) && (param_val < 8)) {
+ val = (param_val << 5) | (param_id);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_SET, val);
+ } else {
+ mutex_lock(&spec->chipio_mutex);
+ if (chipio_send(codec, VENDOR_CHIPIO_STATUS, 0) == 0) {
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_EX_ID_SET,
+ param_id);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_EX_VALUE_SET,
+ param_val);
+ }
+ mutex_unlock(&spec->chipio_mutex);
+ }
+}
+
+/*
+ * Set chip parameters through the chip I/O widget. NO MUTEX.
+ */
+static void chipio_set_control_param_no_mutex(struct hda_codec *codec,
+ enum control_param_id param_id, int param_val)
+{
+ int val;
+
+ if ((param_id < 32) && (param_val < 8)) {
+ val = (param_val << 5) | (param_id);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_SET, val);
+ } else {
+ if (chipio_send(codec, VENDOR_CHIPIO_STATUS, 0) == 0) {
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_EX_ID_SET,
+ param_id);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_EX_VALUE_SET,
+ param_val);
+ }
+ }
+}
+/*
+ * Connect stream to a source point, and then connect
+ * that source point to a destination point.
+ */
+static void chipio_set_stream_source_dest(struct hda_codec *codec,
+ int streamid, int source_point, int dest_point)
+{
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAM_ID, streamid);
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAM_SOURCE_CONN_POINT, source_point);
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAM_DEST_CONN_POINT, dest_point);
+}
+
+/*
+ * Set number of channels in the selected stream.
+ */
+static void chipio_set_stream_channels(struct hda_codec *codec,
+ int streamid, unsigned int channels)
+{
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAM_ID, streamid);
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAMS_CHANNELS, channels);
+}
+
+/*
+ * Enable/Disable audio stream.
+ */
+static void chipio_set_stream_control(struct hda_codec *codec,
+ int streamid, int enable)
+{
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAM_ID, streamid);
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_STREAM_CONTROL, enable);
+}
+
+
+/*
+ * Set sampling rate of the connection point. NO MUTEX.
+ */
+static void chipio_set_conn_rate_no_mutex(struct hda_codec *codec,
+ int connid, enum ca0132_sample_rate rate)
+{
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_CONN_POINT_ID, connid);
+ chipio_set_control_param_no_mutex(codec,
+ CONTROL_PARAM_CONN_POINT_SAMPLE_RATE, rate);
+}
+
+/*
+ * Set sampling rate of the connection point.
+ */
+static void chipio_set_conn_rate(struct hda_codec *codec,
+ int connid, enum ca0132_sample_rate rate)
+{
+ chipio_set_control_param(codec, CONTROL_PARAM_CONN_POINT_ID, connid);
+ chipio_set_control_param(codec, CONTROL_PARAM_CONN_POINT_SAMPLE_RATE,
+ rate);
+}
+
+/*
+ * Writes to the 8051's internal address space directly instead of indirectly,
+ * giving access to the special function registers located at addresses
+ * 0x80-0xFF.
+ */
+static void chipio_8051_write_direct(struct hda_codec *codec,
+ unsigned int addr, unsigned int data)
+{
+ unsigned int verb;
+
+ verb = VENDOR_CHIPIO_8051_WRITE_DIRECT | data;
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, verb, addr);
+}
+
+/*
+ * Enable clocks.
+ */
+static void chipio_enable_clocks(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xff);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 5);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0x0b);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 6);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xff);
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+/*
+ * CA0132 DSP IO stuffs
+ */
+static int dspio_send(struct hda_codec *codec, unsigned int reg,
+ unsigned int data)
+{
+ int res;
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+
+ /* send bits of data specified by reg to dsp */
+ do {
+ res = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, reg, data);
+ if ((res >= 0) && (res != VENDOR_STATUS_DSPIO_BUSY))
+ return res;
+ msleep(20);
+ } while (time_before(jiffies, timeout));
+
+ return -EIO;
+}
+
+/*
+ * Wait for DSP to be ready for commands
+ */
+static void dspio_write_wait(struct hda_codec *codec)
+{
+ int status;
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+
+ do {
+ status = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0,
+ VENDOR_DSPIO_STATUS, 0);
+ if ((status == VENDOR_STATUS_DSPIO_OK) ||
+ (status == VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY))
+ break;
+ msleep(1);
+ } while (time_before(jiffies, timeout));
+}
+
+/*
+ * Write SCP data to DSP
+ */
+static int dspio_write(struct hda_codec *codec, unsigned int scp_data)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int status;
+
+ dspio_write_wait(codec);
+
+ mutex_lock(&spec->chipio_mutex);
+ status = dspio_send(codec, VENDOR_DSPIO_SCP_WRITE_DATA_LOW,
+ scp_data & 0xffff);
+ if (status < 0)
+ goto error;
+
+ status = dspio_send(codec, VENDOR_DSPIO_SCP_WRITE_DATA_HIGH,
+ scp_data >> 16);
+ if (status < 0)
+ goto error;
+
+ /* OK, now check if the write itself has executed*/
+ status = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0,
+ VENDOR_DSPIO_STATUS, 0);
+error:
+ mutex_unlock(&spec->chipio_mutex);
+
+ return (status == VENDOR_STATUS_DSPIO_SCP_COMMAND_QUEUE_FULL) ?
+ -EIO : 0;
+}
+
+/*
+ * Write multiple SCP data to DSP
+ */
+static int dspio_write_multiple(struct hda_codec *codec,
+ unsigned int *buffer, unsigned int size)
+{
+ int status = 0;
+ unsigned int count;
+
+ if (buffer == NULL)
+ return -EINVAL;
+
+ count = 0;
+ while (count < size) {
+ status = dspio_write(codec, *buffer++);
+ if (status != 0)
+ break;
+ count++;
+ }
+
+ return status;
+}
+
+static int dspio_read(struct hda_codec *codec, unsigned int *data)
+{
+ int status;
+
+ status = dspio_send(codec, VENDOR_DSPIO_SCP_POST_READ_DATA, 0);
+ if (status == -EIO)
+ return status;
+
+ status = dspio_send(codec, VENDOR_DSPIO_STATUS, 0);
+ if (status == -EIO ||
+ status == VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY)
+ return -EIO;
+
+ *data = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0,
+ VENDOR_DSPIO_SCP_READ_DATA, 0);
+
+ return 0;
+}
+
+static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer,
+ unsigned int *buf_size, unsigned int size_count)
+{
+ int status = 0;
+ unsigned int size = *buf_size;
+ unsigned int count;
+ unsigned int skip_count;
+ unsigned int dummy;
+
+ if (buffer == NULL)
+ return -1;
+
+ count = 0;
+ while (count < size && count < size_count) {
+ status = dspio_read(codec, buffer++);
+ if (status != 0)
+ break;
+ count++;
+ }
+
+ skip_count = count;
+ if (status == 0) {
+ while (skip_count < size) {
+ status = dspio_read(codec, &dummy);
+ if (status != 0)
+ break;
+ skip_count++;
+ }
+ }
+ *buf_size = count;
+
+ return status;
+}
+
+/*
+ * Construct the SCP header using corresponding fields
+ */
+static inline unsigned int
+make_scp_header(unsigned int target_id, unsigned int source_id,
+ unsigned int get_flag, unsigned int req,
+ unsigned int device_flag, unsigned int resp_flag,
+ unsigned int error_flag, unsigned int data_size)
+{
+ unsigned int header = 0;
+
+ header = (data_size & 0x1f) << 27;
+ header |= (error_flag & 0x01) << 26;
+ header |= (resp_flag & 0x01) << 25;
+ header |= (device_flag & 0x01) << 24;
+ header |= (req & 0x7f) << 17;
+ header |= (get_flag & 0x01) << 16;
+ header |= (source_id & 0xff) << 8;
+ header |= target_id & 0xff;
+
+ return header;
+}
+
+/*
+ * Extract corresponding fields from SCP header
+ */
+static inline void
+extract_scp_header(unsigned int header,
+ unsigned int *target_id, unsigned int *source_id,
+ unsigned int *get_flag, unsigned int *req,
+ unsigned int *device_flag, unsigned int *resp_flag,
+ unsigned int *error_flag, unsigned int *data_size)
+{
+ if (data_size)
+ *data_size = (header >> 27) & 0x1f;
+ if (error_flag)
+ *error_flag = (header >> 26) & 0x01;
+ if (resp_flag)
+ *resp_flag = (header >> 25) & 0x01;
+ if (device_flag)
+ *device_flag = (header >> 24) & 0x01;
+ if (req)
+ *req = (header >> 17) & 0x7f;
+ if (get_flag)
+ *get_flag = (header >> 16) & 0x01;
+ if (source_id)
+ *source_id = (header >> 8) & 0xff;
+ if (target_id)
+ *target_id = header & 0xff;
+}
+
+#define SCP_MAX_DATA_WORDS (16)
+
+/* Structure to contain any SCP message */
+struct scp_msg {
+ unsigned int hdr;
+ unsigned int data[SCP_MAX_DATA_WORDS];
+};
+
+static void dspio_clear_response_queue(struct hda_codec *codec)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+ unsigned int dummy = 0;
+ int status;
+
+ /* clear all from the response queue */
+ do {
+ status = dspio_read(codec, &dummy);
+ } while (status == 0 && time_before(jiffies, timeout));
+}
+
+static int dspio_get_response_data(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int data = 0;
+ unsigned int count;
+
+ if (dspio_read(codec, &data) < 0)
+ return -EIO;
+
+ if ((data & 0x00ffffff) == spec->wait_scp_header) {
+ spec->scp_resp_header = data;
+ spec->scp_resp_count = data >> 27;
+ count = spec->wait_num_data;
+ dspio_read_multiple(codec, spec->scp_resp_data,
+ &spec->scp_resp_count, count);
+ return 0;
+ }
+
+ return -EIO;
+}
+
+/*
+ * Send SCP message to DSP
+ */
+static int dspio_send_scp_message(struct hda_codec *codec,
+ unsigned char *send_buf,
+ unsigned int send_buf_size,
+ unsigned char *return_buf,
+ unsigned int return_buf_size,
+ unsigned int *bytes_returned)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int status = -1;
+ unsigned int scp_send_size = 0;
+ unsigned int total_size;
+ bool waiting_for_resp = false;
+ unsigned int header;
+ struct scp_msg *ret_msg;
+ unsigned int resp_src_id, resp_target_id;
+ unsigned int data_size, src_id, target_id, get_flag, device_flag;
+
+ if (bytes_returned)
+ *bytes_returned = 0;
+
+ /* get scp header from buffer */
+ header = *((unsigned int *)send_buf);
+ extract_scp_header(header, &target_id, &src_id, &get_flag, NULL,
+ &device_flag, NULL, NULL, &data_size);
+ scp_send_size = data_size + 1;
+ total_size = (scp_send_size * 4);
+
+ if (send_buf_size < total_size)
+ return -EINVAL;
+
+ if (get_flag || device_flag) {
+ if (!return_buf || return_buf_size < 4 || !bytes_returned)
+ return -EINVAL;
+
+ spec->wait_scp_header = *((unsigned int *)send_buf);
+
+ /* swap source id with target id */
+ resp_target_id = src_id;
+ resp_src_id = target_id;
+ spec->wait_scp_header &= 0xffff0000;
+ spec->wait_scp_header |= (resp_src_id << 8) | (resp_target_id);
+ spec->wait_num_data = return_buf_size/sizeof(unsigned int) - 1;
+ spec->wait_scp = 1;
+ waiting_for_resp = true;
+ }
+
+ status = dspio_write_multiple(codec, (unsigned int *)send_buf,
+ scp_send_size);
+ if (status < 0) {
+ spec->wait_scp = 0;
+ return status;
+ }
+
+ if (waiting_for_resp) {
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+ memset(return_buf, 0, return_buf_size);
+ do {
+ msleep(20);
+ } while (spec->wait_scp && time_before(jiffies, timeout));
+ waiting_for_resp = false;
+ if (!spec->wait_scp) {
+ ret_msg = (struct scp_msg *)return_buf;
+ memcpy(&ret_msg->hdr, &spec->scp_resp_header, 4);
+ memcpy(&ret_msg->data, spec->scp_resp_data,
+ spec->wait_num_data);
+ *bytes_returned = (spec->scp_resp_count + 1) * 4;
+ status = 0;
+ } else {
+ status = -EIO;
+ }
+ spec->wait_scp = 0;
+ }
+
+ return status;
+}
+
+/**
+ * Prepare and send the SCP message to DSP
+ * @codec: the HDA codec
+ * @mod_id: ID of the DSP module to send the command
+ * @src_id: ID of the source
+ * @req: ID of request to send to the DSP module
+ * @dir: SET or GET
+ * @data: pointer to the data to send with the request, request specific
+ * @len: length of the data, in bytes
+ * @reply: point to the buffer to hold data returned for a reply
+ * @reply_len: length of the reply buffer returned from GET
+ *
+ * Returns zero or a negative error code.
+ */
+static int dspio_scp(struct hda_codec *codec,
+ int mod_id, int src_id, int req, int dir, const void *data,
+ unsigned int len, void *reply, unsigned int *reply_len)
+{
+ int status = 0;
+ struct scp_msg scp_send, scp_reply;
+ unsigned int ret_bytes, send_size, ret_size;
+ unsigned int send_get_flag, reply_resp_flag, reply_error_flag;
+ unsigned int reply_data_size;
+
+ memset(&scp_send, 0, sizeof(scp_send));
+ memset(&scp_reply, 0, sizeof(scp_reply));
+
+ if ((len != 0 && data == NULL) || (len > SCP_MAX_DATA_WORDS))
+ return -EINVAL;
+
+ if (dir == SCP_GET && reply == NULL) {
+ codec_dbg(codec, "dspio_scp get but has no buffer\n");
+ return -EINVAL;
+ }
+
+ if (reply != NULL && (reply_len == NULL || (*reply_len == 0))) {
+ codec_dbg(codec, "dspio_scp bad resp buf len parms\n");
+ return -EINVAL;
+ }
+
+ scp_send.hdr = make_scp_header(mod_id, src_id, (dir == SCP_GET), req,
+ 0, 0, 0, len/sizeof(unsigned int));
+ if (data != NULL && len > 0) {
+ len = min((unsigned int)(sizeof(scp_send.data)), len);
+ memcpy(scp_send.data, data, len);
+ }
+
+ ret_bytes = 0;
+ send_size = sizeof(unsigned int) + len;
+ status = dspio_send_scp_message(codec, (unsigned char *)&scp_send,
+ send_size, (unsigned char *)&scp_reply,
+ sizeof(scp_reply), &ret_bytes);
+
+ if (status < 0) {
+ codec_dbg(codec, "dspio_scp: send scp msg failed\n");
+ return status;
+ }
+
+ /* extract send and reply headers members */
+ extract_scp_header(scp_send.hdr, NULL, NULL, &send_get_flag,
+ NULL, NULL, NULL, NULL, NULL);
+ extract_scp_header(scp_reply.hdr, NULL, NULL, NULL, NULL, NULL,
+ &reply_resp_flag, &reply_error_flag,
+ &reply_data_size);
+
+ if (!send_get_flag)
+ return 0;
+
+ if (reply_resp_flag && !reply_error_flag) {
+ ret_size = (ret_bytes - sizeof(scp_reply.hdr))
+ / sizeof(unsigned int);
+
+ if (*reply_len < ret_size*sizeof(unsigned int)) {
+ codec_dbg(codec, "reply too long for buf\n");
+ return -EINVAL;
+ } else if (ret_size != reply_data_size) {
+ codec_dbg(codec, "RetLen and HdrLen .NE.\n");
+ return -EINVAL;
+ } else if (!reply) {
+ codec_dbg(codec, "NULL reply\n");
+ return -EINVAL;
+ } else {
+ *reply_len = ret_size*sizeof(unsigned int);
+ memcpy(reply, scp_reply.data, *reply_len);
+ }
+ } else {
+ codec_dbg(codec, "reply ill-formed or errflag set\n");
+ return -EIO;
+ }
+
+ return status;
+}
+
+/*
+ * Set DSP parameters
+ */
+static int dspio_set_param(struct hda_codec *codec, int mod_id,
+ int src_id, int req, const void *data, unsigned int len)
+{
+ return dspio_scp(codec, mod_id, src_id, req, SCP_SET, data, len, NULL,
+ NULL);
+}
+
+static int dspio_set_uint_param(struct hda_codec *codec, int mod_id,
+ int req, const unsigned int data)
+{
+ return dspio_set_param(codec, mod_id, 0x20, req, &data,
+ sizeof(unsigned int));
+}
+
+static int dspio_set_uint_param_no_source(struct hda_codec *codec, int mod_id,
+ int req, const unsigned int data)
+{
+ return dspio_set_param(codec, mod_id, 0x00, req, &data,
+ sizeof(unsigned int));
+}
+
+/*
+ * Allocate a DSP DMA channel via an SCP message
+ */
+static int dspio_alloc_dma_chan(struct hda_codec *codec, unsigned int *dma_chan)
+{
+ int status = 0;
+ unsigned int size = sizeof(*dma_chan);
+
+ codec_dbg(codec, " dspio_alloc_dma_chan() -- begin\n");
+ status = dspio_scp(codec, MASTERCONTROL, 0x20,
+ MASTERCONTROL_ALLOC_DMA_CHAN, SCP_GET, NULL, 0,
+ dma_chan, &size);
+
+ if (status < 0) {
+ codec_dbg(codec, "dspio_alloc_dma_chan: SCP Failed\n");
+ return status;
+ }
+
+ if ((*dma_chan + 1) == 0) {
+ codec_dbg(codec, "no free dma channels to allocate\n");
+ return -EBUSY;
+ }
+
+ codec_dbg(codec, "dspio_alloc_dma_chan: chan=%d\n", *dma_chan);
+ codec_dbg(codec, " dspio_alloc_dma_chan() -- complete\n");
+
+ return status;
+}
+
+/*
+ * Free a DSP DMA via an SCP message
+ */
+static int dspio_free_dma_chan(struct hda_codec *codec, unsigned int dma_chan)
+{
+ int status = 0;
+ unsigned int dummy = 0;
+
+ codec_dbg(codec, " dspio_free_dma_chan() -- begin\n");
+ codec_dbg(codec, "dspio_free_dma_chan: chan=%d\n", dma_chan);
+
+ status = dspio_scp(codec, MASTERCONTROL, 0x20,
+ MASTERCONTROL_ALLOC_DMA_CHAN, SCP_SET, &dma_chan,
+ sizeof(dma_chan), NULL, &dummy);
+
+ if (status < 0) {
+ codec_dbg(codec, "dspio_free_dma_chan: SCP Failed\n");
+ return status;
+ }
+
+ codec_dbg(codec, " dspio_free_dma_chan() -- complete\n");
+
+ return status;
+}
+
+/*
+ * (Re)start the DSP
+ */
+static int dsp_set_run_state(struct hda_codec *codec)
+{
+ unsigned int dbg_ctrl_reg;
+ unsigned int halt_state;
+ int err;
+
+ err = chipio_read(codec, DSP_DBGCNTL_INST_OFFSET, &dbg_ctrl_reg);
+ if (err < 0)
+ return err;
+
+ halt_state = (dbg_ctrl_reg & DSP_DBGCNTL_STATE_MASK) >>
+ DSP_DBGCNTL_STATE_LOBIT;
+
+ if (halt_state != 0) {
+ dbg_ctrl_reg &= ~((halt_state << DSP_DBGCNTL_SS_LOBIT) &
+ DSP_DBGCNTL_SS_MASK);
+ err = chipio_write(codec, DSP_DBGCNTL_INST_OFFSET,
+ dbg_ctrl_reg);
+ if (err < 0)
+ return err;
+
+ dbg_ctrl_reg |= (halt_state << DSP_DBGCNTL_EXEC_LOBIT) &
+ DSP_DBGCNTL_EXEC_MASK;
+ err = chipio_write(codec, DSP_DBGCNTL_INST_OFFSET,
+ dbg_ctrl_reg);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Reset the DSP
+ */
+static int dsp_reset(struct hda_codec *codec)
+{
+ unsigned int res;
+ int retry = 20;
+
+ codec_dbg(codec, "dsp_reset\n");
+ do {
+ res = dspio_send(codec, VENDOR_DSPIO_DSP_INIT, 0);
+ retry--;
+ } while (res == -EIO && retry);
+
+ if (!retry) {
+ codec_dbg(codec, "dsp_reset timeout\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Convert chip address to DSP address
+ */
+static unsigned int dsp_chip_to_dsp_addx(unsigned int chip_addx,
+ bool *code, bool *yram)
+{
+ *code = *yram = false;
+
+ if (UC_RANGE(chip_addx, 1)) {
+ *code = true;
+ return UC_OFF(chip_addx);
+ } else if (X_RANGE_ALL(chip_addx, 1)) {
+ return X_OFF(chip_addx);
+ } else if (Y_RANGE_ALL(chip_addx, 1)) {
+ *yram = true;
+ return Y_OFF(chip_addx);
+ }
+
+ return INVALID_CHIP_ADDRESS;
+}
+
+/*
+ * Check if the DSP DMA is active
+ */
+static bool dsp_is_dma_active(struct hda_codec *codec, unsigned int dma_chan)
+{
+ unsigned int dma_chnlstart_reg;
+
+ chipio_read(codec, DSPDMAC_CHNLSTART_INST_OFFSET, &dma_chnlstart_reg);
+
+ return ((dma_chnlstart_reg & (1 <<
+ (DSPDMAC_CHNLSTART_EN_LOBIT + dma_chan))) != 0);
+}
+
+static int dsp_dma_setup_common(struct hda_codec *codec,
+ unsigned int chip_addx,
+ unsigned int dma_chan,
+ unsigned int port_map_mask,
+ bool ovly)
+{
+ int status = 0;
+ unsigned int chnl_prop;
+ unsigned int dsp_addx;
+ unsigned int active;
+ bool code, yram;
+
+ codec_dbg(codec, "-- dsp_dma_setup_common() -- Begin ---------\n");
+
+ if (dma_chan >= DSPDMAC_DMA_CFG_CHANNEL_COUNT) {
+ codec_dbg(codec, "dma chan num invalid\n");
+ return -EINVAL;
+ }
+
+ if (dsp_is_dma_active(codec, dma_chan)) {
+ codec_dbg(codec, "dma already active\n");
+ return -EBUSY;
+ }
+
+ dsp_addx = dsp_chip_to_dsp_addx(chip_addx, &code, &yram);
+
+ if (dsp_addx == INVALID_CHIP_ADDRESS) {
+ codec_dbg(codec, "invalid chip addr\n");
+ return -ENXIO;
+ }
+
+ chnl_prop = DSPDMAC_CHNLPROP_AC_MASK;
+ active = 0;
+
+ codec_dbg(codec, " dsp_dma_setup_common() start reg pgm\n");
+
+ if (ovly) {
+ status = chipio_read(codec, DSPDMAC_CHNLPROP_INST_OFFSET,
+ &chnl_prop);
+
+ if (status < 0) {
+ codec_dbg(codec, "read CHNLPROP Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, "dsp_dma_setup_common() Read CHNLPROP\n");
+ }
+
+ if (!code)
+ chnl_prop &= ~(1 << (DSPDMAC_CHNLPROP_MSPCE_LOBIT + dma_chan));
+ else
+ chnl_prop |= (1 << (DSPDMAC_CHNLPROP_MSPCE_LOBIT + dma_chan));
+
+ chnl_prop &= ~(1 << (DSPDMAC_CHNLPROP_DCON_LOBIT + dma_chan));
+
+ status = chipio_write(codec, DSPDMAC_CHNLPROP_INST_OFFSET, chnl_prop);
+ if (status < 0) {
+ codec_dbg(codec, "write CHNLPROP Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_dma_setup_common() Write CHNLPROP\n");
+
+ if (ovly) {
+ status = chipio_read(codec, DSPDMAC_ACTIVE_INST_OFFSET,
+ &active);
+
+ if (status < 0) {
+ codec_dbg(codec, "read ACTIVE Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, "dsp_dma_setup_common() Read ACTIVE\n");
+ }
+
+ active &= (~(1 << (DSPDMAC_ACTIVE_AAR_LOBIT + dma_chan))) &
+ DSPDMAC_ACTIVE_AAR_MASK;
+
+ status = chipio_write(codec, DSPDMAC_ACTIVE_INST_OFFSET, active);
+ if (status < 0) {
+ codec_dbg(codec, "write ACTIVE Reg fail\n");
+ return status;
+ }
+
+ codec_dbg(codec, " dsp_dma_setup_common() Write ACTIVE\n");
+
+ status = chipio_write(codec, DSPDMAC_AUDCHSEL_INST_OFFSET(dma_chan),
+ port_map_mask);
+ if (status < 0) {
+ codec_dbg(codec, "write AUDCHSEL Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_dma_setup_common() Write AUDCHSEL\n");
+
+ status = chipio_write(codec, DSPDMAC_IRQCNT_INST_OFFSET(dma_chan),
+ DSPDMAC_IRQCNT_BICNT_MASK | DSPDMAC_IRQCNT_CICNT_MASK);
+ if (status < 0) {
+ codec_dbg(codec, "write IRQCNT Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_dma_setup_common() Write IRQCNT\n");
+
+ codec_dbg(codec,
+ "ChipA=0x%x,DspA=0x%x,dmaCh=%u, "
+ "CHSEL=0x%x,CHPROP=0x%x,Active=0x%x\n",
+ chip_addx, dsp_addx, dma_chan,
+ port_map_mask, chnl_prop, active);
+
+ codec_dbg(codec, "-- dsp_dma_setup_common() -- Complete ------\n");
+
+ return 0;
+}
+
+/*
+ * Setup the DSP DMA per-transfer-specific registers
+ */
+static int dsp_dma_setup(struct hda_codec *codec,
+ unsigned int chip_addx,
+ unsigned int count,
+ unsigned int dma_chan)
+{
+ int status = 0;
+ bool code, yram;
+ unsigned int dsp_addx;
+ unsigned int addr_field;
+ unsigned int incr_field;
+ unsigned int base_cnt;
+ unsigned int cur_cnt;
+ unsigned int dma_cfg = 0;
+ unsigned int adr_ofs = 0;
+ unsigned int xfr_cnt = 0;
+ const unsigned int max_dma_count = 1 << (DSPDMAC_XFRCNT_BCNT_HIBIT -
+ DSPDMAC_XFRCNT_BCNT_LOBIT + 1);
+
+ codec_dbg(codec, "-- dsp_dma_setup() -- Begin ---------\n");
+
+ if (count > max_dma_count) {
+ codec_dbg(codec, "count too big\n");
+ return -EINVAL;
+ }
+
+ dsp_addx = dsp_chip_to_dsp_addx(chip_addx, &code, &yram);
+ if (dsp_addx == INVALID_CHIP_ADDRESS) {
+ codec_dbg(codec, "invalid chip addr\n");
+ return -ENXIO;
+ }
+
+ codec_dbg(codec, " dsp_dma_setup() start reg pgm\n");
+
+ addr_field = dsp_addx << DSPDMAC_DMACFG_DBADR_LOBIT;
+ incr_field = 0;
+
+ if (!code) {
+ addr_field <<= 1;
+ if (yram)
+ addr_field |= (1 << DSPDMAC_DMACFG_DBADR_LOBIT);
+
+ incr_field = (1 << DSPDMAC_DMACFG_AINCR_LOBIT);
+ }
+
+ dma_cfg = addr_field + incr_field;
+ status = chipio_write(codec, DSPDMAC_DMACFG_INST_OFFSET(dma_chan),
+ dma_cfg);
+ if (status < 0) {
+ codec_dbg(codec, "write DMACFG Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_dma_setup() Write DMACFG\n");
+
+ adr_ofs = (count - 1) << (DSPDMAC_DSPADROFS_BOFS_LOBIT +
+ (code ? 0 : 1));
+
+ status = chipio_write(codec, DSPDMAC_DSPADROFS_INST_OFFSET(dma_chan),
+ adr_ofs);
+ if (status < 0) {
+ codec_dbg(codec, "write DSPADROFS Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_dma_setup() Write DSPADROFS\n");
+
+ base_cnt = (count - 1) << DSPDMAC_XFRCNT_BCNT_LOBIT;
+
+ cur_cnt = (count - 1) << DSPDMAC_XFRCNT_CCNT_LOBIT;
+
+ xfr_cnt = base_cnt | cur_cnt;
+
+ status = chipio_write(codec,
+ DSPDMAC_XFRCNT_INST_OFFSET(dma_chan), xfr_cnt);
+ if (status < 0) {
+ codec_dbg(codec, "write XFRCNT Reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_dma_setup() Write XFRCNT\n");
+
+ codec_dbg(codec,
+ "ChipA=0x%x, cnt=0x%x, DMACFG=0x%x, "
+ "ADROFS=0x%x, XFRCNT=0x%x\n",
+ chip_addx, count, dma_cfg, adr_ofs, xfr_cnt);
+
+ codec_dbg(codec, "-- dsp_dma_setup() -- Complete ---------\n");
+
+ return 0;
+}
+
+/*
+ * Start the DSP DMA
+ */
+static int dsp_dma_start(struct hda_codec *codec,
+ unsigned int dma_chan, bool ovly)
+{
+ unsigned int reg = 0;
+ int status = 0;
+
+ codec_dbg(codec, "-- dsp_dma_start() -- Begin ---------\n");
+
+ if (ovly) {
+ status = chipio_read(codec,
+ DSPDMAC_CHNLSTART_INST_OFFSET, &reg);
+
+ if (status < 0) {
+ codec_dbg(codec, "read CHNLSTART reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, "-- dsp_dma_start() Read CHNLSTART\n");
+
+ reg &= ~(DSPDMAC_CHNLSTART_EN_MASK |
+ DSPDMAC_CHNLSTART_DIS_MASK);
+ }
+
+ status = chipio_write(codec, DSPDMAC_CHNLSTART_INST_OFFSET,
+ reg | (1 << (dma_chan + DSPDMAC_CHNLSTART_EN_LOBIT)));
+ if (status < 0) {
+ codec_dbg(codec, "write CHNLSTART reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, "-- dsp_dma_start() -- Complete ---------\n");
+
+ return status;
+}
+
+/*
+ * Stop the DSP DMA
+ */
+static int dsp_dma_stop(struct hda_codec *codec,
+ unsigned int dma_chan, bool ovly)
+{
+ unsigned int reg = 0;
+ int status = 0;
+
+ codec_dbg(codec, "-- dsp_dma_stop() -- Begin ---------\n");
+
+ if (ovly) {
+ status = chipio_read(codec,
+ DSPDMAC_CHNLSTART_INST_OFFSET, &reg);
+
+ if (status < 0) {
+ codec_dbg(codec, "read CHNLSTART reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, "-- dsp_dma_stop() Read CHNLSTART\n");
+ reg &= ~(DSPDMAC_CHNLSTART_EN_MASK |
+ DSPDMAC_CHNLSTART_DIS_MASK);
+ }
+
+ status = chipio_write(codec, DSPDMAC_CHNLSTART_INST_OFFSET,
+ reg | (1 << (dma_chan + DSPDMAC_CHNLSTART_DIS_LOBIT)));
+ if (status < 0) {
+ codec_dbg(codec, "write CHNLSTART reg fail\n");
+ return status;
+ }
+ codec_dbg(codec, "-- dsp_dma_stop() -- Complete ---------\n");
+
+ return status;
+}
+
+/**
+ * Allocate router ports
+ *
+ * @codec: the HDA codec
+ * @num_chans: number of channels in the stream
+ * @ports_per_channel: number of ports per channel
+ * @start_device: start device
+ * @port_map: pointer to the port list to hold the allocated ports
+ *
+ * Returns zero or a negative error code.
+ */
+static int dsp_allocate_router_ports(struct hda_codec *codec,
+ unsigned int num_chans,
+ unsigned int ports_per_channel,
+ unsigned int start_device,
+ unsigned int *port_map)
+{
+ int status = 0;
+ int res;
+ u8 val;
+
+ status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0);
+ if (status < 0)
+ return status;
+
+ val = start_device << 6;
+ val |= (ports_per_channel - 1) << 4;
+ val |= num_chans - 1;
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PORT_ALLOC_CONFIG_SET,
+ val);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PORT_ALLOC_SET,
+ MEM_CONNID_DSP);
+
+ status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0);
+ if (status < 0)
+ return status;
+
+ res = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PORT_ALLOC_GET, 0);
+
+ *port_map = res;
+
+ return (res < 0) ? res : 0;
+}
+
+/*
+ * Free router ports
+ */
+static int dsp_free_router_ports(struct hda_codec *codec)
+{
+ int status = 0;
+
+ status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0);
+ if (status < 0)
+ return status;
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PORT_FREE_SET,
+ MEM_CONNID_DSP);
+
+ status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0);
+
+ return status;
+}
+
+/*
+ * Allocate DSP ports for the download stream
+ */
+static int dsp_allocate_ports(struct hda_codec *codec,
+ unsigned int num_chans,
+ unsigned int rate_multi, unsigned int *port_map)
+{
+ int status;
+
+ codec_dbg(codec, " dsp_allocate_ports() -- begin\n");
+
+ if ((rate_multi != 1) && (rate_multi != 2) && (rate_multi != 4)) {
+ codec_dbg(codec, "bad rate multiple\n");
+ return -EINVAL;
+ }
+
+ status = dsp_allocate_router_ports(codec, num_chans,
+ rate_multi, 0, port_map);
+
+ codec_dbg(codec, " dsp_allocate_ports() -- complete\n");
+
+ return status;
+}
+
+static int dsp_allocate_ports_format(struct hda_codec *codec,
+ const unsigned short fmt,
+ unsigned int *port_map)
+{
+ int status;
+ unsigned int num_chans;
+
+ unsigned int sample_rate_div = ((get_hdafmt_rate(fmt) >> 0) & 3) + 1;
+ unsigned int sample_rate_mul = ((get_hdafmt_rate(fmt) >> 3) & 3) + 1;
+ unsigned int rate_multi = sample_rate_mul / sample_rate_div;
+
+ if ((rate_multi != 1) && (rate_multi != 2) && (rate_multi != 4)) {
+ codec_dbg(codec, "bad rate multiple\n");
+ return -EINVAL;
+ }
+
+ num_chans = get_hdafmt_chs(fmt) + 1;
+
+ status = dsp_allocate_ports(codec, num_chans, rate_multi, port_map);
+
+ return status;
+}
+
+/*
+ * free DSP ports
+ */
+static int dsp_free_ports(struct hda_codec *codec)
+{
+ int status;
+
+ codec_dbg(codec, " dsp_free_ports() -- begin\n");
+
+ status = dsp_free_router_ports(codec);
+ if (status < 0) {
+ codec_dbg(codec, "free router ports fail\n");
+ return status;
+ }
+ codec_dbg(codec, " dsp_free_ports() -- complete\n");
+
+ return status;
+}
+
+/*
+ * HDA DMA engine stuffs for DSP code download
+ */
+struct dma_engine {
+ struct hda_codec *codec;
+ unsigned short m_converter_format;
+ struct snd_dma_buffer *dmab;
+ unsigned int buf_size;
+};
+
+
+enum dma_state {
+ DMA_STATE_STOP = 0,
+ DMA_STATE_RUN = 1
+};
+
+static int dma_convert_to_hda_format(struct hda_codec *codec,
+ unsigned int sample_rate,
+ unsigned short channels,
+ unsigned short *hda_format)
+{
+ unsigned int format_val;
+
+ format_val = snd_hdac_calc_stream_format(sample_rate,
+ channels, SNDRV_PCM_FORMAT_S32_LE, 32, 0);
+
+ if (hda_format)
+ *hda_format = (unsigned short)format_val;
+
+ return 0;
+}
+
+/*
+ * Reset DMA for DSP download
+ */
+static int dma_reset(struct dma_engine *dma)
+{
+ struct hda_codec *codec = dma->codec;
+ struct ca0132_spec *spec = codec->spec;
+ int status;
+
+ if (dma->dmab->area)
+ snd_hda_codec_load_dsp_cleanup(codec, dma->dmab);
+
+ status = snd_hda_codec_load_dsp_prepare(codec,
+ dma->m_converter_format,
+ dma->buf_size,
+ dma->dmab);
+ if (status < 0)
+ return status;
+ spec->dsp_stream_id = status;
+ return 0;
+}
+
+static int dma_set_state(struct dma_engine *dma, enum dma_state state)
+{
+ bool cmd;
+
+ switch (state) {
+ case DMA_STATE_STOP:
+ cmd = false;
+ break;
+ case DMA_STATE_RUN:
+ cmd = true;
+ break;
+ default:
+ return 0;
+ }
+
+ snd_hda_codec_load_dsp_trigger(dma->codec, cmd);
+ return 0;
+}
+
+static unsigned int dma_get_buffer_size(struct dma_engine *dma)
+{
+ return dma->dmab->bytes;
+}
+
+static unsigned char *dma_get_buffer_addr(struct dma_engine *dma)
+{
+ return dma->dmab->area;
+}
+
+static int dma_xfer(struct dma_engine *dma,
+ const unsigned int *data,
+ unsigned int count)
+{
+ memcpy(dma->dmab->area, data, count);
+ return 0;
+}
+
+static void dma_get_converter_format(
+ struct dma_engine *dma,
+ unsigned short *format)
+{
+ if (format)
+ *format = dma->m_converter_format;
+}
+
+static unsigned int dma_get_stream_id(struct dma_engine *dma)
+{
+ struct ca0132_spec *spec = dma->codec->spec;
+
+ return spec->dsp_stream_id;
+}
+
+struct dsp_image_seg {
+ u32 magic;
+ u32 chip_addr;
+ u32 count;
+ u32 data[];
+};
+
+static const u32 g_magic_value = 0x4c46584d;
+static const u32 g_chip_addr_magic_value = 0xFFFFFF01;
+
+static bool is_valid(const struct dsp_image_seg *p)
+{
+ return p->magic == g_magic_value;
+}
+
+static bool is_hci_prog_list_seg(const struct dsp_image_seg *p)
+{
+ return g_chip_addr_magic_value == p->chip_addr;
+}
+
+static bool is_last(const struct dsp_image_seg *p)
+{
+ return p->count == 0;
+}
+
+static size_t dsp_sizeof(const struct dsp_image_seg *p)
+{
+ return struct_size(p, data, p->count);
+}
+
+static const struct dsp_image_seg *get_next_seg_ptr(
+ const struct dsp_image_seg *p)
+{
+ return (struct dsp_image_seg *)((unsigned char *)(p) + dsp_sizeof(p));
+}
+
+/*
+ * CA0132 chip DSP transfer stuffs. For DSP download.
+ */
+#define INVALID_DMA_CHANNEL (~0U)
+
+/*
+ * Program a list of address/data pairs via the ChipIO widget.
+ * The segment data is in the format of successive pairs of words.
+ * These are repeated as indicated by the segment's count field.
+ */
+static int dspxfr_hci_write(struct hda_codec *codec,
+ const struct dsp_image_seg *fls)
+{
+ int status;
+ const u32 *data;
+ unsigned int count;
+
+ if (fls == NULL || fls->chip_addr != g_chip_addr_magic_value) {
+ codec_dbg(codec, "hci_write invalid params\n");
+ return -EINVAL;
+ }
+
+ count = fls->count;
+ data = (u32 *)(fls->data);
+ while (count >= 2) {
+ status = chipio_write(codec, data[0], data[1]);
+ if (status < 0) {
+ codec_dbg(codec, "hci_write chipio failed\n");
+ return status;
+ }
+ count -= 2;
+ data += 2;
+ }
+ return 0;
+}
+
+/**
+ * Write a block of data into DSP code or data RAM using pre-allocated
+ * DMA engine.
+ *
+ * @codec: the HDA codec
+ * @fls: pointer to a fast load image
+ * @reloc: Relocation address for loading single-segment overlays, or 0 for
+ * no relocation
+ * @dma_engine: pointer to DMA engine to be used for DSP download
+ * @dma_chan: The number of DMA channels used for DSP download
+ * @port_map_mask: port mapping
+ * @ovly: TRUE if overlay format is required
+ *
+ * Returns zero or a negative error code.
+ */
+static int dspxfr_one_seg(struct hda_codec *codec,
+ const struct dsp_image_seg *fls,
+ unsigned int reloc,
+ struct dma_engine *dma_engine,
+ unsigned int dma_chan,
+ unsigned int port_map_mask,
+ bool ovly)
+{
+ int status = 0;
+ bool comm_dma_setup_done = false;
+ const unsigned int *data;
+ unsigned int chip_addx;
+ unsigned int words_to_write;
+ unsigned int buffer_size_words;
+ unsigned char *buffer_addx;
+ unsigned short hda_format;
+ unsigned int sample_rate_div;
+ unsigned int sample_rate_mul;
+ unsigned int num_chans;
+ unsigned int hda_frame_size_words;
+ unsigned int remainder_words;
+ const u32 *data_remainder;
+ u32 chip_addx_remainder;
+ unsigned int run_size_words;
+ const struct dsp_image_seg *hci_write = NULL;
+ unsigned long timeout;
+ bool dma_active;
+
+ if (fls == NULL)
+ return -EINVAL;
+ if (is_hci_prog_list_seg(fls)) {
+ hci_write = fls;
+ fls = get_next_seg_ptr(fls);
+ }
+
+ if (hci_write && (!fls || is_last(fls))) {
+ codec_dbg(codec, "hci_write\n");
+ return dspxfr_hci_write(codec, hci_write);
+ }
+
+ if (fls == NULL || dma_engine == NULL || port_map_mask == 0) {
+ codec_dbg(codec, "Invalid Params\n");
+ return -EINVAL;
+ }
+
+ data = fls->data;
+ chip_addx = fls->chip_addr;
+ words_to_write = fls->count;
+
+ if (!words_to_write)
+ return hci_write ? dspxfr_hci_write(codec, hci_write) : 0;
+ if (reloc)
+ chip_addx = (chip_addx & (0xFFFF0000 << 2)) + (reloc << 2);
+
+ if (!UC_RANGE(chip_addx, words_to_write) &&
+ !X_RANGE_ALL(chip_addx, words_to_write) &&
+ !Y_RANGE_ALL(chip_addx, words_to_write)) {
+ codec_dbg(codec, "Invalid chip_addx Params\n");
+ return -EINVAL;
+ }
+
+ buffer_size_words = (unsigned int)dma_get_buffer_size(dma_engine) /
+ sizeof(u32);
+
+ buffer_addx = dma_get_buffer_addr(dma_engine);
+
+ if (buffer_addx == NULL) {
+ codec_dbg(codec, "dma_engine buffer NULL\n");
+ return -EINVAL;
+ }
+
+ dma_get_converter_format(dma_engine, &hda_format);
+ sample_rate_div = ((get_hdafmt_rate(hda_format) >> 0) & 3) + 1;
+ sample_rate_mul = ((get_hdafmt_rate(hda_format) >> 3) & 3) + 1;
+ num_chans = get_hdafmt_chs(hda_format) + 1;
+
+ hda_frame_size_words = ((sample_rate_div == 0) ? 0 :
+ (num_chans * sample_rate_mul / sample_rate_div));
+
+ if (hda_frame_size_words == 0) {
+ codec_dbg(codec, "frmsz zero\n");
+ return -EINVAL;
+ }
+
+ buffer_size_words = min(buffer_size_words,
+ (unsigned int)(UC_RANGE(chip_addx, 1) ?
+ 65536 : 32768));
+ buffer_size_words -= buffer_size_words % hda_frame_size_words;
+ codec_dbg(codec,
+ "chpadr=0x%08x frmsz=%u nchan=%u "
+ "rate_mul=%u div=%u bufsz=%u\n",
+ chip_addx, hda_frame_size_words, num_chans,
+ sample_rate_mul, sample_rate_div, buffer_size_words);
+
+ if (buffer_size_words < hda_frame_size_words) {
+ codec_dbg(codec, "dspxfr_one_seg:failed\n");
+ return -EINVAL;
+ }
+
+ remainder_words = words_to_write % hda_frame_size_words;
+ data_remainder = data;
+ chip_addx_remainder = chip_addx;
+
+ data += remainder_words;
+ chip_addx += remainder_words*sizeof(u32);
+ words_to_write -= remainder_words;
+
+ while (words_to_write != 0) {
+ run_size_words = min(buffer_size_words, words_to_write);
+ codec_dbg(codec, "dspxfr (seg loop)cnt=%u rs=%u remainder=%u\n",
+ words_to_write, run_size_words, remainder_words);
+ dma_xfer(dma_engine, data, run_size_words*sizeof(u32));
+ if (!comm_dma_setup_done) {
+ status = dsp_dma_stop(codec, dma_chan, ovly);
+ if (status < 0)
+ return status;
+ status = dsp_dma_setup_common(codec, chip_addx,
+ dma_chan, port_map_mask, ovly);
+ if (status < 0)
+ return status;
+ comm_dma_setup_done = true;
+ }
+
+ status = dsp_dma_setup(codec, chip_addx,
+ run_size_words, dma_chan);
+ if (status < 0)
+ return status;
+ status = dsp_dma_start(codec, dma_chan, ovly);
+ if (status < 0)
+ return status;
+ if (!dsp_is_dma_active(codec, dma_chan)) {
+ codec_dbg(codec, "dspxfr:DMA did not start\n");
+ return -EIO;
+ }
+ status = dma_set_state(dma_engine, DMA_STATE_RUN);
+ if (status < 0)
+ return status;
+ if (remainder_words != 0) {
+ status = chipio_write_multiple(codec,
+ chip_addx_remainder,
+ data_remainder,
+ remainder_words);
+ if (status < 0)
+ return status;
+ remainder_words = 0;
+ }
+ if (hci_write) {
+ status = dspxfr_hci_write(codec, hci_write);
+ if (status < 0)
+ return status;
+ hci_write = NULL;
+ }
+
+ timeout = jiffies + msecs_to_jiffies(2000);
+ do {
+ dma_active = dsp_is_dma_active(codec, dma_chan);
+ if (!dma_active)
+ break;
+ msleep(20);
+ } while (time_before(jiffies, timeout));
+ if (dma_active)
+ break;
+
+ codec_dbg(codec, "+++++ DMA complete\n");
+ dma_set_state(dma_engine, DMA_STATE_STOP);
+ status = dma_reset(dma_engine);
+
+ if (status < 0)
+ return status;
+
+ data += run_size_words;
+ chip_addx += run_size_words*sizeof(u32);
+ words_to_write -= run_size_words;
+ }
+
+ if (remainder_words != 0) {
+ status = chipio_write_multiple(codec, chip_addx_remainder,
+ data_remainder, remainder_words);
+ }
+
+ return status;
+}
+
+/**
+ * Write the entire DSP image of a DSP code/data overlay to DSP memories
+ *
+ * @codec: the HDA codec
+ * @fls_data: pointer to a fast load image
+ * @reloc: Relocation address for loading single-segment overlays, or 0 for
+ * no relocation
+ * @sample_rate: sampling rate of the stream used for DSP download
+ * @channels: channels of the stream used for DSP download
+ * @ovly: TRUE if overlay format is required
+ *
+ * Returns zero or a negative error code.
+ */
+static int dspxfr_image(struct hda_codec *codec,
+ const struct dsp_image_seg *fls_data,
+ unsigned int reloc,
+ unsigned int sample_rate,
+ unsigned short channels,
+ bool ovly)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int status;
+ unsigned short hda_format = 0;
+ unsigned int response;
+ unsigned char stream_id = 0;
+ struct dma_engine *dma_engine;
+ unsigned int dma_chan;
+ unsigned int port_map_mask;
+
+ if (fls_data == NULL)
+ return -EINVAL;
+
+ dma_engine = kzalloc(sizeof(*dma_engine), GFP_KERNEL);
+ if (!dma_engine)
+ return -ENOMEM;
+
+ dma_engine->dmab = kzalloc(sizeof(*dma_engine->dmab), GFP_KERNEL);
+ if (!dma_engine->dmab) {
+ kfree(dma_engine);
+ return -ENOMEM;
+ }
+
+ dma_engine->codec = codec;
+ dma_convert_to_hda_format(codec, sample_rate, channels, &hda_format);
+ dma_engine->m_converter_format = hda_format;
+ dma_engine->buf_size = (ovly ? DSP_DMA_WRITE_BUFLEN_OVLY :
+ DSP_DMA_WRITE_BUFLEN_INIT) * 2;
+
+ dma_chan = ovly ? INVALID_DMA_CHANNEL : 0;
+
+ status = codec_set_converter_format(codec, WIDGET_CHIP_CTRL,
+ hda_format, &response);
+
+ if (status < 0) {
+ codec_dbg(codec, "set converter format fail\n");
+ goto exit;
+ }
+
+ status = snd_hda_codec_load_dsp_prepare(codec,
+ dma_engine->m_converter_format,
+ dma_engine->buf_size,
+ dma_engine->dmab);
+ if (status < 0)
+ goto exit;
+ spec->dsp_stream_id = status;
+
+ if (ovly) {
+ status = dspio_alloc_dma_chan(codec, &dma_chan);
+ if (status < 0) {
+ codec_dbg(codec, "alloc dmachan fail\n");
+ dma_chan = INVALID_DMA_CHANNEL;
+ goto exit;
+ }
+ }
+
+ port_map_mask = 0;
+ status = dsp_allocate_ports_format(codec, hda_format,
+ &port_map_mask);
+ if (status < 0) {
+ codec_dbg(codec, "alloc ports fail\n");
+ goto exit;
+ }
+
+ stream_id = dma_get_stream_id(dma_engine);
+ status = codec_set_converter_stream_channel(codec,
+ WIDGET_CHIP_CTRL, stream_id, 0, &response);
+ if (status < 0) {
+ codec_dbg(codec, "set stream chan fail\n");
+ goto exit;
+ }
+
+ while ((fls_data != NULL) && !is_last(fls_data)) {
+ if (!is_valid(fls_data)) {
+ codec_dbg(codec, "FLS check fail\n");
+ status = -EINVAL;
+ goto exit;
+ }
+ status = dspxfr_one_seg(codec, fls_data, reloc,
+ dma_engine, dma_chan,
+ port_map_mask, ovly);
+ if (status < 0)
+ break;
+
+ if (is_hci_prog_list_seg(fls_data))
+ fls_data = get_next_seg_ptr(fls_data);
+
+ if ((fls_data != NULL) && !is_last(fls_data))
+ fls_data = get_next_seg_ptr(fls_data);
+ }
+
+ if (port_map_mask != 0)
+ status = dsp_free_ports(codec);
+
+ if (status < 0)
+ goto exit;
+
+ status = codec_set_converter_stream_channel(codec,
+ WIDGET_CHIP_CTRL, 0, 0, &response);
+
+exit:
+ if (ovly && (dma_chan != INVALID_DMA_CHANNEL))
+ dspio_free_dma_chan(codec, dma_chan);
+
+ if (dma_engine->dmab->area)
+ snd_hda_codec_load_dsp_cleanup(codec, dma_engine->dmab);
+ kfree(dma_engine->dmab);
+ kfree(dma_engine);
+
+ return status;
+}
+
+/*
+ * CA0132 DSP download stuffs.
+ */
+static void dspload_post_setup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ codec_dbg(codec, "---- dspload_post_setup ------\n");
+ if (!ca0132_use_alt_functions(spec)) {
+ /*set DSP speaker to 2.0 configuration*/
+ chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x18), 0x08080080);
+ chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x19), 0x3f800000);
+
+ /*update write pointer*/
+ chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x29), 0x00000002);
+ }
+}
+
+/**
+ * dspload_image - Download DSP from a DSP Image Fast Load structure.
+ *
+ * @codec: the HDA codec
+ * @fls: pointer to a fast load image
+ * @ovly: TRUE if overlay format is required
+ * @reloc: Relocation address for loading single-segment overlays, or 0 for
+ * no relocation
+ * @autostart: TRUE if DSP starts after loading; ignored if ovly is TRUE
+ * @router_chans: number of audio router channels to be allocated (0 means use
+ * internal defaults; max is 32)
+ *
+ * Download DSP from a DSP Image Fast Load structure. This structure is a
+ * linear, non-constant sized element array of structures, each of which
+ * contain the count of the data to be loaded, the data itself, and the
+ * corresponding starting chip address of the starting data location.
+ * Returns zero or a negative error code.
+ */
+static int dspload_image(struct hda_codec *codec,
+ const struct dsp_image_seg *fls,
+ bool ovly,
+ unsigned int reloc,
+ bool autostart,
+ int router_chans)
+{
+ int status = 0;
+ unsigned int sample_rate;
+ unsigned short channels;
+
+ codec_dbg(codec, "---- dspload_image begin ------\n");
+ if (router_chans == 0) {
+ if (!ovly)
+ router_chans = DMA_TRANSFER_FRAME_SIZE_NWORDS;
+ else
+ router_chans = DMA_OVERLAY_FRAME_SIZE_NWORDS;
+ }
+
+ sample_rate = 48000;
+ channels = (unsigned short)router_chans;
+
+ while (channels > 16) {
+ sample_rate *= 2;
+ channels /= 2;
+ }
+
+ do {
+ codec_dbg(codec, "Ready to program DMA\n");
+ if (!ovly)
+ status = dsp_reset(codec);
+
+ if (status < 0)
+ break;
+
+ codec_dbg(codec, "dsp_reset() complete\n");
+ status = dspxfr_image(codec, fls, reloc, sample_rate, channels,
+ ovly);
+
+ if (status < 0)
+ break;
+
+ codec_dbg(codec, "dspxfr_image() complete\n");
+ if (autostart && !ovly) {
+ dspload_post_setup(codec);
+ status = dsp_set_run_state(codec);
+ }
+
+ codec_dbg(codec, "LOAD FINISHED\n");
+ } while (0);
+
+ return status;
+}
+
+#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP
+static bool dspload_is_loaded(struct hda_codec *codec)
+{
+ unsigned int data = 0;
+ int status = 0;
+
+ status = chipio_read(codec, 0x40004, &data);
+ if ((status < 0) || (data != 1))
+ return false;
+
+ return true;
+}
+#else
+#define dspload_is_loaded(codec) false
+#endif
+
+static bool dspload_wait_loaded(struct hda_codec *codec)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(2000);
+
+ do {
+ if (dspload_is_loaded(codec)) {
+ codec_info(codec, "ca0132 DSP downloaded and running\n");
+ return true;
+ }
+ msleep(20);
+ } while (time_before(jiffies, timeout));
+
+ codec_err(codec, "ca0132 failed to download DSP\n");
+ return false;
+}
+
+/*
+ * ca0113 related functions. The ca0113 acts as the HDA bus for the pci-e
+ * based cards, and has a second mmio region, region2, that's used for special
+ * commands.
+ */
+
+/*
+ * For cards with PCI-E region2 (Sound Blaster Z/ZxR, Recon3D, and AE-5)
+ * the mmio address 0x320 is used to set GPIO pins. The format for the data
+ * The first eight bits are just the number of the pin. So far, I've only seen
+ * this number go to 7.
+ * AE-5 note: The AE-5 seems to use pins 2 and 3 to somehow set the color value
+ * of the on-card LED. It seems to use pin 2 for data, then toggles 3 to on and
+ * then off to send that bit.
+ */
+static void ca0113_mmio_gpio_set(struct hda_codec *codec, unsigned int gpio_pin,
+ bool enable)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned short gpio_data;
+
+ gpio_data = gpio_pin & 0xF;
+ gpio_data |= ((enable << 8) & 0x100);
+
+ writew(gpio_data, spec->mem_base + 0x320);
+}
+
+/*
+ * Special pci region2 commands that are only used by the AE-5. They follow
+ * a set format, and require reads at certain points to seemingly 'clear'
+ * the response data. My first tests didn't do these reads, and would cause
+ * the card to get locked up until the memory was read. These commands
+ * seem to work with three distinct values that I've taken to calling group,
+ * target-id, and value.
+ */
+static void ca0113_mmio_command_set(struct hda_codec *codec, unsigned int group,
+ unsigned int target, unsigned int value)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int write_val;
+
+ writel(0x0000007e, spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+ writel(0x0000005a, spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+
+ writel(0x00800005, spec->mem_base + 0x20c);
+ writel(group, spec->mem_base + 0x804);
+
+ writel(0x00800005, spec->mem_base + 0x20c);
+ write_val = (target & 0xff);
+ write_val |= (value << 8);
+
+
+ writel(write_val, spec->mem_base + 0x204);
+ /*
+ * Need delay here or else it goes too fast and works inconsistently.
+ */
+ msleep(20);
+
+ readl(spec->mem_base + 0x860);
+ readl(spec->mem_base + 0x854);
+ readl(spec->mem_base + 0x840);
+
+ writel(0x00800004, spec->mem_base + 0x20c);
+ writel(0x00000000, spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+}
+
+/*
+ * This second type of command is used for setting the sound filter type.
+ */
+static void ca0113_mmio_command_set_type2(struct hda_codec *codec,
+ unsigned int group, unsigned int target, unsigned int value)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int write_val;
+
+ writel(0x0000007e, spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+ writel(0x0000005a, spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+
+ writel(0x00800003, spec->mem_base + 0x20c);
+ writel(group, spec->mem_base + 0x804);
+
+ writel(0x00800005, spec->mem_base + 0x20c);
+ write_val = (target & 0xff);
+ write_val |= (value << 8);
+
+
+ writel(write_val, spec->mem_base + 0x204);
+ msleep(20);
+ readl(spec->mem_base + 0x860);
+ readl(spec->mem_base + 0x854);
+ readl(spec->mem_base + 0x840);
+
+ writel(0x00800004, spec->mem_base + 0x20c);
+ writel(0x00000000, spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+ readl(spec->mem_base + 0x210);
+}
+
+/*
+ * Setup GPIO for the other variants of Core3D.
+ */
+
+/*
+ * Sets up the GPIO pins so that they are discoverable. If this isn't done,
+ * the card shows as having no GPIO pins.
+ */
+static void ca0132_gpio_init(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_AE5:
+ case QUIRK_AE7:
+ snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53);
+ snd_hda_codec_write(codec, 0x01, 0, 0x790, 0x23);
+ break;
+ case QUIRK_R3DI:
+ snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x5B);
+ break;
+ default:
+ break;
+ }
+
+}
+
+/* Sets the GPIO for audio output. */
+static void ca0132_gpio_setup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DIRECTION, 0x07);
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_MASK, 0x07);
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, 0x04);
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, 0x06);
+ break;
+ case QUIRK_R3DI:
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DIRECTION, 0x1E);
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_MASK, 0x1F);
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, 0x0C);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * GPIO control functions for the Recon3D integrated.
+ */
+
+enum r3di_gpio_bit {
+ /* Bit 1 - Switch between front/rear mic. 0 = rear, 1 = front */
+ R3DI_MIC_SELECT_BIT = 1,
+ /* Bit 2 - Switch between headphone/line out. 0 = Headphone, 1 = Line */
+ R3DI_OUT_SELECT_BIT = 2,
+ /*
+ * I dunno what this actually does, but it stays on until the dsp
+ * is downloaded.
+ */
+ R3DI_GPIO_DSP_DOWNLOADING = 3,
+ /*
+ * Same as above, no clue what it does, but it comes on after the dsp
+ * is downloaded.
+ */
+ R3DI_GPIO_DSP_DOWNLOADED = 4
+};
+
+enum r3di_mic_select {
+ /* Set GPIO bit 1 to 0 for rear mic */
+ R3DI_REAR_MIC = 0,
+ /* Set GPIO bit 1 to 1 for front microphone*/
+ R3DI_FRONT_MIC = 1
+};
+
+enum r3di_out_select {
+ /* Set GPIO bit 2 to 0 for headphone */
+ R3DI_HEADPHONE_OUT = 0,
+ /* Set GPIO bit 2 to 1 for speaker */
+ R3DI_LINE_OUT = 1
+};
+enum r3di_dsp_status {
+ /* Set GPIO bit 3 to 1 until DSP is downloaded */
+ R3DI_DSP_DOWNLOADING = 0,
+ /* Set GPIO bit 4 to 1 once DSP is downloaded */
+ R3DI_DSP_DOWNLOADED = 1
+};
+
+
+static void r3di_gpio_mic_set(struct hda_codec *codec,
+ enum r3di_mic_select cur_mic)
+{
+ unsigned int cur_gpio;
+
+ /* Get the current GPIO Data setup */
+ cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0);
+
+ switch (cur_mic) {
+ case R3DI_REAR_MIC:
+ cur_gpio &= ~(1 << R3DI_MIC_SELECT_BIT);
+ break;
+ case R3DI_FRONT_MIC:
+ cur_gpio |= (1 << R3DI_MIC_SELECT_BIT);
+ break;
+ }
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, cur_gpio);
+}
+
+static void r3di_gpio_dsp_status_set(struct hda_codec *codec,
+ enum r3di_dsp_status dsp_status)
+{
+ unsigned int cur_gpio;
+
+ /* Get the current GPIO Data setup */
+ cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0);
+
+ switch (dsp_status) {
+ case R3DI_DSP_DOWNLOADING:
+ cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADING);
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, cur_gpio);
+ break;
+ case R3DI_DSP_DOWNLOADED:
+ /* Set DOWNLOADING bit to 0. */
+ cur_gpio &= ~(1 << R3DI_GPIO_DSP_DOWNLOADING);
+
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, cur_gpio);
+
+ cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADED);
+ break;
+ }
+
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, cur_gpio);
+}
+
+/*
+ * PCM callbacks
+ */
+static int ca0132_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ snd_hda_codec_setup_stream(codec, spec->dacs[0], stream_tag, 0, format);
+
+ return 0;
+}
+
+static int ca0132_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ if (spec->dsp_state == DSP_DOWNLOADING)
+ return 0;
+
+ /*If Playback effects are on, allow stream some time to flush
+ *effects tail*/
+ if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID])
+ msleep(50);
+
+ snd_hda_codec_cleanup_stream(codec, spec->dacs[0]);
+
+ return 0;
+}
+
+static unsigned int ca0132_playback_pcm_delay(struct hda_pcm_stream *info,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int latency = DSP_PLAYBACK_INIT_LATENCY;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return 0;
+
+ /* Add latency if playback enhancement and either effect is enabled. */
+ if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) {
+ if ((spec->effects_switch[SURROUND - EFFECT_START_NID]) ||
+ (spec->effects_switch[DIALOG_PLUS - EFFECT_START_NID]))
+ latency += DSP_PLAY_ENHANCEMENT_LATENCY;
+ }
+
+ /* Applying Speaker EQ adds latency as well. */
+ if (spec->cur_out_type == SPEAKER_OUT)
+ latency += DSP_SPEAKER_OUT_LATENCY;
+
+ return (latency * runtime->rate) / 1000;
+}
+
+/*
+ * Digital out
+ */
+static int ca0132_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int ca0132_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+ stream_tag, format, substream);
+}
+
+static int ca0132_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+}
+
+static int ca0132_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int ca0132_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid,
+ stream_tag, 0, format);
+
+ return 0;
+}
+
+static int ca0132_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ if (spec->dsp_state == DSP_DOWNLOADING)
+ return 0;
+
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ return 0;
+}
+
+static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int latency = DSP_CAPTURE_INIT_LATENCY;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return 0;
+
+ if (spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID])
+ latency += DSP_CRYSTAL_VOICE_LATENCY;
+
+ return (latency * runtime->rate) / 1000;
+}
+
+/*
+ * Controls stuffs.
+ */
+
+/*
+ * Mixer controls helpers.
+ */
+#define CA0132_CODEC_VOL_MONO(xname, nid, channel, dir) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
+ .info = ca0132_volume_info, \
+ .get = ca0132_volume_get, \
+ .put = ca0132_volume_put, \
+ .tlv = { .c = ca0132_volume_tlv }, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) }
+
+/*
+ * Creates a mixer control that uses defaults of HDA_CODEC_VOL except for the
+ * volume put, which is used for setting the DSP volume. This was done because
+ * the ca0132 functions were taking too much time and causing lag.
+ */
+#define CA0132_ALT_CODEC_VOL_MONO(xname, nid, channel, dir) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
+ .info = snd_hda_mixer_amp_volume_info, \
+ .get = snd_hda_mixer_amp_volume_get, \
+ .put = ca0132_alt_volume_put, \
+ .tlv = { .c = snd_hda_mixer_amp_tlv }, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) }
+
+#define CA0132_CODEC_MUTE_MONO(xname, nid, channel, dir) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .info = snd_hda_mixer_amp_switch_info, \
+ .get = ca0132_switch_get, \
+ .put = ca0132_switch_put, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) }
+
+/* stereo */
+#define CA0132_CODEC_VOL(xname, nid, dir) \
+ CA0132_CODEC_VOL_MONO(xname, nid, 3, dir)
+#define CA0132_ALT_CODEC_VOL(xname, nid, dir) \
+ CA0132_ALT_CODEC_VOL_MONO(xname, nid, 3, dir)
+#define CA0132_CODEC_MUTE(xname, nid, dir) \
+ CA0132_CODEC_MUTE_MONO(xname, nid, 3, dir)
+
+/* lookup tables */
+/*
+ * Lookup table with decibel values for the DSP. When volume is changed in
+ * Windows, the DSP is also sent the dB value in floating point. In Windows,
+ * these values have decimal points, probably because the Windows driver
+ * actually uses floating point. We can't here, so I made a lookup table of
+ * values -90 to 9. -90 is the lowest decibel value for both the ADC's and the
+ * DAC's, and 9 is the maximum.
+ */
+static const unsigned int float_vol_db_lookup[] = {
+0xC2B40000, 0xC2B20000, 0xC2B00000, 0xC2AE0000, 0xC2AC0000, 0xC2AA0000,
+0xC2A80000, 0xC2A60000, 0xC2A40000, 0xC2A20000, 0xC2A00000, 0xC29E0000,
+0xC29C0000, 0xC29A0000, 0xC2980000, 0xC2960000, 0xC2940000, 0xC2920000,
+0xC2900000, 0xC28E0000, 0xC28C0000, 0xC28A0000, 0xC2880000, 0xC2860000,
+0xC2840000, 0xC2820000, 0xC2800000, 0xC27C0000, 0xC2780000, 0xC2740000,
+0xC2700000, 0xC26C0000, 0xC2680000, 0xC2640000, 0xC2600000, 0xC25C0000,
+0xC2580000, 0xC2540000, 0xC2500000, 0xC24C0000, 0xC2480000, 0xC2440000,
+0xC2400000, 0xC23C0000, 0xC2380000, 0xC2340000, 0xC2300000, 0xC22C0000,
+0xC2280000, 0xC2240000, 0xC2200000, 0xC21C0000, 0xC2180000, 0xC2140000,
+0xC2100000, 0xC20C0000, 0xC2080000, 0xC2040000, 0xC2000000, 0xC1F80000,
+0xC1F00000, 0xC1E80000, 0xC1E00000, 0xC1D80000, 0xC1D00000, 0xC1C80000,
+0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000,
+0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000,
+0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000,
+0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000,
+0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000,
+0x40C00000, 0x40E00000, 0x41000000, 0x41100000
+};
+
+/*
+ * This table counts from float 0 to 1 in increments of .01, which is
+ * useful for a few different sliders.
+ */
+static const unsigned int float_zero_to_one_lookup[] = {
+0x00000000, 0x3C23D70A, 0x3CA3D70A, 0x3CF5C28F, 0x3D23D70A, 0x3D4CCCCD,
+0x3D75C28F, 0x3D8F5C29, 0x3DA3D70A, 0x3DB851EC, 0x3DCCCCCD, 0x3DE147AE,
+0x3DF5C28F, 0x3E051EB8, 0x3E0F5C29, 0x3E19999A, 0x3E23D70A, 0x3E2E147B,
+0x3E3851EC, 0x3E428F5C, 0x3E4CCCCD, 0x3E570A3D, 0x3E6147AE, 0x3E6B851F,
+0x3E75C28F, 0x3E800000, 0x3E851EB8, 0x3E8A3D71, 0x3E8F5C29, 0x3E947AE1,
+0x3E99999A, 0x3E9EB852, 0x3EA3D70A, 0x3EA8F5C3, 0x3EAE147B, 0x3EB33333,
+0x3EB851EC, 0x3EBD70A4, 0x3EC28F5C, 0x3EC7AE14, 0x3ECCCCCD, 0x3ED1EB85,
+0x3ED70A3D, 0x3EDC28F6, 0x3EE147AE, 0x3EE66666, 0x3EEB851F, 0x3EF0A3D7,
+0x3EF5C28F, 0x3EFAE148, 0x3F000000, 0x3F028F5C, 0x3F051EB8, 0x3F07AE14,
+0x3F0A3D71, 0x3F0CCCCD, 0x3F0F5C29, 0x3F11EB85, 0x3F147AE1, 0x3F170A3D,
+0x3F19999A, 0x3F1C28F6, 0x3F1EB852, 0x3F2147AE, 0x3F23D70A, 0x3F266666,
+0x3F28F5C3, 0x3F2B851F, 0x3F2E147B, 0x3F30A3D7, 0x3F333333, 0x3F35C28F,
+0x3F3851EC, 0x3F3AE148, 0x3F3D70A4, 0x3F400000, 0x3F428F5C, 0x3F451EB8,
+0x3F47AE14, 0x3F4A3D71, 0x3F4CCCCD, 0x3F4F5C29, 0x3F51EB85, 0x3F547AE1,
+0x3F570A3D, 0x3F59999A, 0x3F5C28F6, 0x3F5EB852, 0x3F6147AE, 0x3F63D70A,
+0x3F666666, 0x3F68F5C3, 0x3F6B851F, 0x3F6E147B, 0x3F70A3D7, 0x3F733333,
+0x3F75C28F, 0x3F7851EC, 0x3F7AE148, 0x3F7D70A4, 0x3F800000
+};
+
+/*
+ * This table counts from float 10 to 1000, which is the range of the x-bass
+ * crossover slider in Windows.
+ */
+static const unsigned int float_xbass_xover_lookup[] = {
+0x41200000, 0x41A00000, 0x41F00000, 0x42200000, 0x42480000, 0x42700000,
+0x428C0000, 0x42A00000, 0x42B40000, 0x42C80000, 0x42DC0000, 0x42F00000,
+0x43020000, 0x430C0000, 0x43160000, 0x43200000, 0x432A0000, 0x43340000,
+0x433E0000, 0x43480000, 0x43520000, 0x435C0000, 0x43660000, 0x43700000,
+0x437A0000, 0x43820000, 0x43870000, 0x438C0000, 0x43910000, 0x43960000,
+0x439B0000, 0x43A00000, 0x43A50000, 0x43AA0000, 0x43AF0000, 0x43B40000,
+0x43B90000, 0x43BE0000, 0x43C30000, 0x43C80000, 0x43CD0000, 0x43D20000,
+0x43D70000, 0x43DC0000, 0x43E10000, 0x43E60000, 0x43EB0000, 0x43F00000,
+0x43F50000, 0x43FA0000, 0x43FF0000, 0x44020000, 0x44048000, 0x44070000,
+0x44098000, 0x440C0000, 0x440E8000, 0x44110000, 0x44138000, 0x44160000,
+0x44188000, 0x441B0000, 0x441D8000, 0x44200000, 0x44228000, 0x44250000,
+0x44278000, 0x442A0000, 0x442C8000, 0x442F0000, 0x44318000, 0x44340000,
+0x44368000, 0x44390000, 0x443B8000, 0x443E0000, 0x44408000, 0x44430000,
+0x44458000, 0x44480000, 0x444A8000, 0x444D0000, 0x444F8000, 0x44520000,
+0x44548000, 0x44570000, 0x44598000, 0x445C0000, 0x445E8000, 0x44610000,
+0x44638000, 0x44660000, 0x44688000, 0x446B0000, 0x446D8000, 0x44700000,
+0x44728000, 0x44750000, 0x44778000, 0x447A0000
+};
+
+/* The following are for tuning of products */
+#ifdef ENABLE_TUNING_CONTROLS
+
+static const unsigned int voice_focus_vals_lookup[] = {
+0x41A00000, 0x41A80000, 0x41B00000, 0x41B80000, 0x41C00000, 0x41C80000,
+0x41D00000, 0x41D80000, 0x41E00000, 0x41E80000, 0x41F00000, 0x41F80000,
+0x42000000, 0x42040000, 0x42080000, 0x420C0000, 0x42100000, 0x42140000,
+0x42180000, 0x421C0000, 0x42200000, 0x42240000, 0x42280000, 0x422C0000,
+0x42300000, 0x42340000, 0x42380000, 0x423C0000, 0x42400000, 0x42440000,
+0x42480000, 0x424C0000, 0x42500000, 0x42540000, 0x42580000, 0x425C0000,
+0x42600000, 0x42640000, 0x42680000, 0x426C0000, 0x42700000, 0x42740000,
+0x42780000, 0x427C0000, 0x42800000, 0x42820000, 0x42840000, 0x42860000,
+0x42880000, 0x428A0000, 0x428C0000, 0x428E0000, 0x42900000, 0x42920000,
+0x42940000, 0x42960000, 0x42980000, 0x429A0000, 0x429C0000, 0x429E0000,
+0x42A00000, 0x42A20000, 0x42A40000, 0x42A60000, 0x42A80000, 0x42AA0000,
+0x42AC0000, 0x42AE0000, 0x42B00000, 0x42B20000, 0x42B40000, 0x42B60000,
+0x42B80000, 0x42BA0000, 0x42BC0000, 0x42BE0000, 0x42C00000, 0x42C20000,
+0x42C40000, 0x42C60000, 0x42C80000, 0x42CA0000, 0x42CC0000, 0x42CE0000,
+0x42D00000, 0x42D20000, 0x42D40000, 0x42D60000, 0x42D80000, 0x42DA0000,
+0x42DC0000, 0x42DE0000, 0x42E00000, 0x42E20000, 0x42E40000, 0x42E60000,
+0x42E80000, 0x42EA0000, 0x42EC0000, 0x42EE0000, 0x42F00000, 0x42F20000,
+0x42F40000, 0x42F60000, 0x42F80000, 0x42FA0000, 0x42FC0000, 0x42FE0000,
+0x43000000, 0x43010000, 0x43020000, 0x43030000, 0x43040000, 0x43050000,
+0x43060000, 0x43070000, 0x43080000, 0x43090000, 0x430A0000, 0x430B0000,
+0x430C0000, 0x430D0000, 0x430E0000, 0x430F0000, 0x43100000, 0x43110000,
+0x43120000, 0x43130000, 0x43140000, 0x43150000, 0x43160000, 0x43170000,
+0x43180000, 0x43190000, 0x431A0000, 0x431B0000, 0x431C0000, 0x431D0000,
+0x431E0000, 0x431F0000, 0x43200000, 0x43210000, 0x43220000, 0x43230000,
+0x43240000, 0x43250000, 0x43260000, 0x43270000, 0x43280000, 0x43290000,
+0x432A0000, 0x432B0000, 0x432C0000, 0x432D0000, 0x432E0000, 0x432F0000,
+0x43300000, 0x43310000, 0x43320000, 0x43330000, 0x43340000
+};
+
+static const unsigned int mic_svm_vals_lookup[] = {
+0x00000000, 0x3C23D70A, 0x3CA3D70A, 0x3CF5C28F, 0x3D23D70A, 0x3D4CCCCD,
+0x3D75C28F, 0x3D8F5C29, 0x3DA3D70A, 0x3DB851EC, 0x3DCCCCCD, 0x3DE147AE,
+0x3DF5C28F, 0x3E051EB8, 0x3E0F5C29, 0x3E19999A, 0x3E23D70A, 0x3E2E147B,
+0x3E3851EC, 0x3E428F5C, 0x3E4CCCCD, 0x3E570A3D, 0x3E6147AE, 0x3E6B851F,
+0x3E75C28F, 0x3E800000, 0x3E851EB8, 0x3E8A3D71, 0x3E8F5C29, 0x3E947AE1,
+0x3E99999A, 0x3E9EB852, 0x3EA3D70A, 0x3EA8F5C3, 0x3EAE147B, 0x3EB33333,
+0x3EB851EC, 0x3EBD70A4, 0x3EC28F5C, 0x3EC7AE14, 0x3ECCCCCD, 0x3ED1EB85,
+0x3ED70A3D, 0x3EDC28F6, 0x3EE147AE, 0x3EE66666, 0x3EEB851F, 0x3EF0A3D7,
+0x3EF5C28F, 0x3EFAE148, 0x3F000000, 0x3F028F5C, 0x3F051EB8, 0x3F07AE14,
+0x3F0A3D71, 0x3F0CCCCD, 0x3F0F5C29, 0x3F11EB85, 0x3F147AE1, 0x3F170A3D,
+0x3F19999A, 0x3F1C28F6, 0x3F1EB852, 0x3F2147AE, 0x3F23D70A, 0x3F266666,
+0x3F28F5C3, 0x3F2B851F, 0x3F2E147B, 0x3F30A3D7, 0x3F333333, 0x3F35C28F,
+0x3F3851EC, 0x3F3AE148, 0x3F3D70A4, 0x3F400000, 0x3F428F5C, 0x3F451EB8,
+0x3F47AE14, 0x3F4A3D71, 0x3F4CCCCD, 0x3F4F5C29, 0x3F51EB85, 0x3F547AE1,
+0x3F570A3D, 0x3F59999A, 0x3F5C28F6, 0x3F5EB852, 0x3F6147AE, 0x3F63D70A,
+0x3F666666, 0x3F68F5C3, 0x3F6B851F, 0x3F6E147B, 0x3F70A3D7, 0x3F733333,
+0x3F75C28F, 0x3F7851EC, 0x3F7AE148, 0x3F7D70A4, 0x3F800000
+};
+
+static const unsigned int equalizer_vals_lookup[] = {
+0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000,
+0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000,
+0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000,
+0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000,
+0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000,
+0x40C00000, 0x40E00000, 0x41000000, 0x41100000, 0x41200000, 0x41300000,
+0x41400000, 0x41500000, 0x41600000, 0x41700000, 0x41800000, 0x41880000,
+0x41900000, 0x41980000, 0x41A00000, 0x41A80000, 0x41B00000, 0x41B80000,
+0x41C00000
+};
+
+static int tuning_ctl_set(struct hda_codec *codec, hda_nid_t nid,
+ const unsigned int *lookup, int idx)
+{
+ int i = 0;
+
+ for (i = 0; i < TUNING_CTLS_COUNT; i++)
+ if (nid == ca0132_tuning_ctls[i].nid)
+ goto found;
+
+ return -EINVAL;
+found:
+ snd_hda_power_up(codec);
+ dspio_set_param(codec, ca0132_tuning_ctls[i].mid, 0x20,
+ ca0132_tuning_ctls[i].req,
+ &(lookup[idx]), sizeof(unsigned int));
+ snd_hda_power_down(codec);
+
+ return 1;
+}
+
+static int tuning_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int idx = nid - TUNING_CTL_START_NID;
+
+ *valp = spec->cur_ctl_vals[idx];
+ return 0;
+}
+
+static int voice_focus_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 20;
+ uinfo->value.integer.max = 180;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int voice_focus_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int idx;
+
+ idx = nid - TUNING_CTL_START_NID;
+ /* any change? */
+ if (spec->cur_ctl_vals[idx] == *valp)
+ return 0;
+
+ spec->cur_ctl_vals[idx] = *valp;
+
+ idx = *valp - 20;
+ tuning_ctl_set(codec, nid, voice_focus_vals_lookup, idx);
+
+ return 1;
+}
+
+static int mic_svm_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int mic_svm_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int idx;
+
+ idx = nid - TUNING_CTL_START_NID;
+ /* any change? */
+ if (spec->cur_ctl_vals[idx] == *valp)
+ return 0;
+
+ spec->cur_ctl_vals[idx] = *valp;
+
+ idx = *valp;
+ tuning_ctl_set(codec, nid, mic_svm_vals_lookup, idx);
+
+ return 0;
+}
+
+static int equalizer_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 48;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int equalizer_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int idx;
+
+ idx = nid - TUNING_CTL_START_NID;
+ /* any change? */
+ if (spec->cur_ctl_vals[idx] == *valp)
+ return 0;
+
+ spec->cur_ctl_vals[idx] = *valp;
+
+ idx = *valp;
+ tuning_ctl_set(codec, nid, equalizer_vals_lookup, idx);
+
+ return 1;
+}
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(voice_focus_db_scale, 2000, 100, 0);
+static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(eq_db_scale, -2400, 100, 0);
+
+static int add_tuning_control(struct hda_codec *codec,
+ hda_nid_t pnid, hda_nid_t nid,
+ const char *name, int dir)
+{
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int type = dir ? HDA_INPUT : HDA_OUTPUT;
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type);
+
+ knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+ knew.tlv.c = 0;
+ knew.tlv.p = 0;
+ switch (pnid) {
+ case VOICE_FOCUS:
+ knew.info = voice_focus_ctl_info;
+ knew.get = tuning_ctl_get;
+ knew.put = voice_focus_ctl_put;
+ knew.tlv.p = voice_focus_db_scale;
+ break;
+ case MIC_SVM:
+ knew.info = mic_svm_ctl_info;
+ knew.get = tuning_ctl_get;
+ knew.put = mic_svm_ctl_put;
+ break;
+ case EQUALIZER:
+ knew.info = equalizer_ctl_info;
+ knew.get = tuning_ctl_get;
+ knew.put = equalizer_ctl_put;
+ knew.tlv.p = eq_db_scale;
+ break;
+ default:
+ return 0;
+ }
+ knew.private_value =
+ HDA_COMPOSE_AMP_VAL(nid, 1, 0, type);
+ sprintf(namestr, "%s %s Volume", name, dirstr[dir]);
+ return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec));
+}
+
+static int add_tuning_ctls(struct hda_codec *codec)
+{
+ int i;
+ int err;
+
+ for (i = 0; i < TUNING_CTLS_COUNT; i++) {
+ err = add_tuning_control(codec,
+ ca0132_tuning_ctls[i].parent_nid,
+ ca0132_tuning_ctls[i].nid,
+ ca0132_tuning_ctls[i].name,
+ ca0132_tuning_ctls[i].direct);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static void ca0132_init_tuning_defaults(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int i;
+
+ /* Wedge Angle defaults to 30. 10 below is 30 - 20. 20 is min. */
+ spec->cur_ctl_vals[WEDGE_ANGLE - TUNING_CTL_START_NID] = 10;
+ /* SVM level defaults to 0.74. */
+ spec->cur_ctl_vals[SVM_LEVEL - TUNING_CTL_START_NID] = 74;
+
+ /* EQ defaults to 0dB. */
+ for (i = 2; i < TUNING_CTLS_COUNT; i++)
+ spec->cur_ctl_vals[i] = 24;
+}
+#endif /*ENABLE_TUNING_CONTROLS*/
+
+/*
+ * Select the active output.
+ * If autodetect is enabled, output will be selected based on jack detection.
+ * If jack inserted, headphone will be selected, else built-in speakers
+ * If autodetect is disabled, output will be selected based on selection.
+ */
+static int ca0132_select_out(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int pin_ctl;
+ int jack_present;
+ int auto_jack;
+ unsigned int tmp;
+ int err;
+
+ codec_dbg(codec, "ca0132_select_out\n");
+
+ snd_hda_power_up_pm(codec);
+
+ auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID];
+
+ if (auto_jack)
+ jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_hp);
+ else
+ jack_present =
+ spec->vnode_lswitch[VNID_HP_SEL - VNODE_START_NID];
+
+ if (jack_present)
+ spec->cur_out_type = HEADPHONE_OUT;
+ else
+ spec->cur_out_type = SPEAKER_OUT;
+
+ if (spec->cur_out_type == SPEAKER_OUT) {
+ codec_dbg(codec, "ca0132_select_out speaker\n");
+ /*speaker out config*/
+ tmp = FLOAT_ONE;
+ err = dspio_set_uint_param(codec, 0x80, 0x04, tmp);
+ if (err < 0)
+ goto exit;
+ /*enable speaker EQ*/
+ tmp = FLOAT_ONE;
+ err = dspio_set_uint_param(codec, 0x8f, 0x00, tmp);
+ if (err < 0)
+ goto exit;
+
+ /* Setup EAPD */
+ snd_hda_codec_write(codec, spec->out_pins[1], 0,
+ VENDOR_CHIPIO_EAPD_SEL_SET, 0x02);
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ VENDOR_CHIPIO_EAPD_SEL_SET, 0x00);
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+
+ /* disable headphone node */
+ pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_set_pin_ctl(codec, spec->out_pins[1],
+ pin_ctl & ~PIN_HP);
+ /* enable speaker node */
+ pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_set_pin_ctl(codec, spec->out_pins[0],
+ pin_ctl | PIN_OUT);
+ } else {
+ codec_dbg(codec, "ca0132_select_out hp\n");
+ /*headphone out config*/
+ tmp = FLOAT_ZERO;
+ err = dspio_set_uint_param(codec, 0x80, 0x04, tmp);
+ if (err < 0)
+ goto exit;
+ /*disable speaker EQ*/
+ tmp = FLOAT_ZERO;
+ err = dspio_set_uint_param(codec, 0x8f, 0x00, tmp);
+ if (err < 0)
+ goto exit;
+
+ /* Setup EAPD */
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ VENDOR_CHIPIO_EAPD_SEL_SET, 0x00);
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+ snd_hda_codec_write(codec, spec->out_pins[1], 0,
+ VENDOR_CHIPIO_EAPD_SEL_SET, 0x02);
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+
+ /* disable speaker*/
+ pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_set_pin_ctl(codec, spec->out_pins[0],
+ pin_ctl & ~PIN_HP);
+ /* enable headphone*/
+ pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_set_pin_ctl(codec, spec->out_pins[1],
+ pin_ctl | PIN_HP);
+ }
+
+exit:
+ snd_hda_power_down_pm(codec);
+
+ return err < 0 ? err : 0;
+}
+
+static int ae5_headphone_gain_set(struct hda_codec *codec, long val);
+static int zxr_headphone_gain_set(struct hda_codec *codec, long val);
+static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val);
+
+static void ae5_mmio_select_out(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ const struct ae_ca0113_output_set *out_cmds;
+ unsigned int i;
+
+ if (ca0132_quirk(spec) == QUIRK_AE5)
+ out_cmds = &ae5_ca0113_output_presets;
+ else
+ out_cmds = &ae7_ca0113_output_presets;
+
+ for (i = 0; i < AE_CA0113_OUT_SET_COMMANDS; i++)
+ ca0113_mmio_command_set(codec, out_cmds->group[i],
+ out_cmds->target[i],
+ out_cmds->vals[spec->cur_out_type][i]);
+}
+
+static int ca0132_alt_set_full_range_speaker(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int quirk = ca0132_quirk(spec);
+ unsigned int tmp;
+ int err;
+
+ /* 2.0/4.0 setup has no LFE channel, so setting full-range does nothing. */
+ if (spec->channel_cfg_val == SPEAKER_CHANNELS_4_0
+ || spec->channel_cfg_val == SPEAKER_CHANNELS_2_0)
+ return 0;
+
+ /* Set front L/R full range. Zero for full-range, one for redirection. */
+ tmp = spec->speaker_range_val[0] ? FLOAT_ZERO : FLOAT_ONE;
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_FULL_RANGE_FRONT_L_R, tmp);
+ if (err < 0)
+ return err;
+
+ /* When setting full-range rear, both rear and center/lfe are set. */
+ tmp = spec->speaker_range_val[1] ? FLOAT_ZERO : FLOAT_ONE;
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_FULL_RANGE_CENTER_LFE, tmp);
+ if (err < 0)
+ return err;
+
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_FULL_RANGE_REAR_L_R, tmp);
+ if (err < 0)
+ return err;
+
+ /*
+ * Only the AE series cards set this value when setting full-range,
+ * and it's always 1.0f.
+ */
+ if (quirk == QUIRK_AE5 || quirk == QUIRK_AE7) {
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_FULL_RANGE_SURROUND_L_R, FLOAT_ONE);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int ca0132_alt_surround_set_bass_redirection(struct hda_codec *codec,
+ bool val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ int err;
+
+ if (val && spec->channel_cfg_val != SPEAKER_CHANNELS_4_0 &&
+ spec->channel_cfg_val != SPEAKER_CHANNELS_2_0)
+ tmp = FLOAT_ONE;
+ else
+ tmp = FLOAT_ZERO;
+
+ err = dspio_set_uint_param(codec, 0x96, SPEAKER_BASS_REDIRECT, tmp);
+ if (err < 0)
+ return err;
+
+ /* If it is enabled, make sure to set the crossover frequency. */
+ if (tmp) {
+ tmp = float_xbass_xover_lookup[spec->xbass_xover_freq];
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_BASS_REDIRECT_XOVER_FREQ, tmp);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * These are the commands needed to setup output on each of the different card
+ * types.
+ */
+static void ca0132_alt_select_out_get_quirk_data(struct hda_codec *codec,
+ const struct ca0132_alt_out_set_quirk_data **quirk_data)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int quirk = ca0132_quirk(spec);
+ unsigned int i;
+
+ *quirk_data = NULL;
+ for (i = 0; i < ARRAY_SIZE(quirk_out_set_data); i++) {
+ if (quirk_out_set_data[i].quirk_id == quirk) {
+ *quirk_data = &quirk_out_set_data[i];
+ return;
+ }
+ }
+}
+
+static int ca0132_alt_select_out_quirk_set(struct hda_codec *codec)
+{
+ const struct ca0132_alt_out_set_quirk_data *quirk_data;
+ const struct ca0132_alt_out_set_info *out_info;
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int i, gpio_data;
+ int err;
+
+ ca0132_alt_select_out_get_quirk_data(codec, &quirk_data);
+ if (!quirk_data)
+ return 0;
+
+ out_info = &quirk_data->out_set_info[spec->cur_out_type];
+ if (quirk_data->is_ae_series)
+ ae5_mmio_select_out(codec);
+
+ if (out_info->has_hda_gpio) {
+ gpio_data = snd_hda_codec_read(codec, codec->core.afg, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+
+ if (out_info->hda_gpio_set)
+ gpio_data |= (1 << out_info->hda_gpio_pin);
+ else
+ gpio_data &= ~(1 << out_info->hda_gpio_pin);
+
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, gpio_data);
+ }
+
+ if (out_info->mmio_gpio_count) {
+ for (i = 0; i < out_info->mmio_gpio_count; i++) {
+ ca0113_mmio_gpio_set(codec, out_info->mmio_gpio_pin[i],
+ out_info->mmio_gpio_set[i]);
+ }
+ }
+
+ if (out_info->scp_cmds_count) {
+ for (i = 0; i < out_info->scp_cmds_count; i++) {
+ err = dspio_set_uint_param(codec,
+ out_info->scp_cmd_mid[i],
+ out_info->scp_cmd_req[i],
+ out_info->scp_cmd_val[i]);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ chipio_set_control_param(codec, 0x0d, out_info->dac2port);
+
+ if (out_info->has_chipio_write) {
+ chipio_write(codec, out_info->chipio_write_addr,
+ out_info->chipio_write_data);
+ }
+
+ if (quirk_data->has_headphone_gain) {
+ if (spec->cur_out_type != HEADPHONE_OUT) {
+ if (quirk_data->is_ae_series)
+ ae5_headphone_gain_set(codec, 2);
+ else
+ zxr_headphone_gain_set(codec, 0);
+ } else {
+ if (quirk_data->is_ae_series)
+ ae5_headphone_gain_set(codec,
+ spec->ae5_headphone_gain_val);
+ else
+ zxr_headphone_gain_set(codec,
+ spec->zxr_gain_set);
+ }
+ }
+
+ return 0;
+}
+
+static void ca0132_set_out_node_pincfg(struct hda_codec *codec, hda_nid_t nid,
+ bool out_enable, bool hp_enable)
+{
+ unsigned int pin_ctl;
+
+ pin_ctl = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+
+ pin_ctl = hp_enable ? pin_ctl | PIN_HP_AMP : pin_ctl & ~PIN_HP_AMP;
+ pin_ctl = out_enable ? pin_ctl | PIN_OUT : pin_ctl & ~PIN_OUT;
+ snd_hda_set_pin_ctl(codec, nid, pin_ctl);
+}
+
+/*
+ * This function behaves similarly to the ca0132_select_out funciton above,
+ * except with a few differences. It adds the ability to select the current
+ * output with an enumerated control "output source" if the auto detect
+ * mute switch is set to off. If the auto detect mute switch is enabled, it
+ * will detect either headphone or lineout(SPEAKER_OUT) from jack detection.
+ * It also adds the ability to auto-detect the front headphone port.
+ */
+static int ca0132_alt_select_out(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp, outfx_set;
+ int jack_present;
+ int auto_jack;
+ int err;
+ /* Default Headphone is rear headphone */
+ hda_nid_t headphone_nid = spec->out_pins[1];
+
+ codec_dbg(codec, "%s\n", __func__);
+
+ snd_hda_power_up_pm(codec);
+
+ auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID];
+
+ /*
+ * If headphone rear or front is plugged in, set to headphone.
+ * If neither is plugged in, set to rear line out. Only if
+ * hp/speaker auto detect is enabled.
+ */
+ if (auto_jack) {
+ jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_hp) ||
+ snd_hda_jack_detect(codec, spec->unsol_tag_front_hp);
+
+ if (jack_present)
+ spec->cur_out_type = HEADPHONE_OUT;
+ else
+ spec->cur_out_type = SPEAKER_OUT;
+ } else
+ spec->cur_out_type = spec->out_enum_val;
+
+ outfx_set = spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID];
+
+ /* Begin DSP output switch, mute DSP volume. */
+ err = dspio_set_uint_param(codec, 0x96, SPEAKER_TUNING_MUTE, FLOAT_ONE);
+ if (err < 0)
+ goto exit;
+
+ if (ca0132_alt_select_out_quirk_set(codec) < 0)
+ goto exit;
+
+ switch (spec->cur_out_type) {
+ case SPEAKER_OUT:
+ codec_dbg(codec, "%s speaker\n", __func__);
+
+ /* Enable EAPD */
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x01);
+
+ /* Disable headphone node. */
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[1], 0, 0);
+ /* Set front L-R to output. */
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[0], 1, 0);
+ /* Set Center/LFE to output. */
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[2], 1, 0);
+ /* Set rear surround to output. */
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[3], 1, 0);
+
+ /*
+ * Without PlayEnhancement being enabled, if we've got a 2.0
+ * setup, set it to floating point eight to disable any DSP
+ * processing effects.
+ */
+ if (!outfx_set && spec->channel_cfg_val == SPEAKER_CHANNELS_2_0)
+ tmp = FLOAT_EIGHT;
+ else
+ tmp = speaker_channel_cfgs[spec->channel_cfg_val].val;
+
+ err = dspio_set_uint_param(codec, 0x80, 0x04, tmp);
+ if (err < 0)
+ goto exit;
+
+ break;
+ case HEADPHONE_OUT:
+ codec_dbg(codec, "%s hp\n", __func__);
+ snd_hda_codec_write(codec, spec->out_pins[0], 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+
+ /* Disable all speaker nodes. */
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[0], 0, 0);
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[2], 0, 0);
+ ca0132_set_out_node_pincfg(codec, spec->out_pins[3], 0, 0);
+
+ /* enable headphone, either front or rear */
+ if (snd_hda_jack_detect(codec, spec->unsol_tag_front_hp))
+ headphone_nid = spec->out_pins[2];
+ else if (snd_hda_jack_detect(codec, spec->unsol_tag_hp))
+ headphone_nid = spec->out_pins[1];
+
+ ca0132_set_out_node_pincfg(codec, headphone_nid, 1, 1);
+
+ if (outfx_set)
+ err = dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE);
+ else
+ err = dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ZERO);
+
+ if (err < 0)
+ goto exit;
+ break;
+ }
+ /*
+ * If output effects are enabled, set the X-Bass effect value again to
+ * make sure that it's properly enabled/disabled for speaker
+ * configurations with an LFE channel.
+ */
+ if (outfx_set)
+ ca0132_effects_set(codec, X_BASS,
+ spec->effects_switch[X_BASS - EFFECT_START_NID]);
+
+ /* Set speaker EQ bypass attenuation to 0. */
+ err = dspio_set_uint_param(codec, 0x8f, 0x01, FLOAT_ZERO);
+ if (err < 0)
+ goto exit;
+
+ /*
+ * Although unused on all cards but the AE series, this is always set
+ * to zero when setting the output.
+ */
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_TUNING_USE_SPEAKER_EQ, FLOAT_ZERO);
+ if (err < 0)
+ goto exit;
+
+ if (spec->cur_out_type == SPEAKER_OUT)
+ err = ca0132_alt_surround_set_bass_redirection(codec,
+ spec->bass_redirection_val);
+ else
+ err = ca0132_alt_surround_set_bass_redirection(codec, 0);
+
+ /* Unmute DSP now that we're done with output selection. */
+ err = dspio_set_uint_param(codec, 0x96,
+ SPEAKER_TUNING_MUTE, FLOAT_ZERO);
+ if (err < 0)
+ goto exit;
+
+ if (spec->cur_out_type == SPEAKER_OUT) {
+ err = ca0132_alt_set_full_range_speaker(codec);
+ if (err < 0)
+ goto exit;
+ }
+
+exit:
+ snd_hda_power_down_pm(codec);
+
+ return err < 0 ? err : 0;
+}
+
+static void ca0132_unsol_hp_delayed(struct work_struct *work)
+{
+ struct ca0132_spec *spec = container_of(
+ to_delayed_work(work), struct ca0132_spec, unsol_hp_work);
+ struct hda_jack_tbl *jack;
+
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_select_out(spec->codec);
+ else
+ ca0132_select_out(spec->codec);
+
+ jack = snd_hda_jack_tbl_get(spec->codec, spec->unsol_tag_hp);
+ if (jack) {
+ jack->block_report = 0;
+ snd_hda_jack_report_sync(spec->codec);
+ }
+}
+
+static void ca0132_set_dmic(struct hda_codec *codec, int enable);
+static int ca0132_mic_boost_set(struct hda_codec *codec, long val);
+static void resume_mic1(struct hda_codec *codec, unsigned int oldval);
+static int stop_mic1(struct hda_codec *codec);
+static int ca0132_cvoice_switch_set(struct hda_codec *codec);
+static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val);
+
+/*
+ * Select the active VIP source
+ */
+static int ca0132_set_vipsource(struct hda_codec *codec, int val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return 0;
+
+ /* if CrystalVoice if off, vipsource should be 0 */
+ if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] ||
+ (val == 0)) {
+ chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ if (spec->cur_mic_type == DIGITAL_MIC)
+ tmp = FLOAT_TWO;
+ else
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x80, 0x05, tmp);
+ } else {
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000);
+ if (spec->cur_mic_type == DIGITAL_MIC)
+ tmp = FLOAT_TWO;
+ else
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x05, tmp);
+ msleep(20);
+ chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, val);
+ }
+
+ return 1;
+}
+
+static int ca0132_alt_set_vipsource(struct hda_codec *codec, int val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return 0;
+
+ codec_dbg(codec, "%s\n", __func__);
+
+ chipio_set_stream_control(codec, 0x03, 0);
+ chipio_set_stream_control(codec, 0x04, 0);
+
+ /* if CrystalVoice is off, vipsource should be 0 */
+ if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] ||
+ (val == 0) || spec->in_enum_val == REAR_LINE_IN) {
+ codec_dbg(codec, "%s: off.", __func__);
+ chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0);
+
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x80, 0x05, tmp);
+
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ chipio_set_conn_rate(codec, 0x0F, SR_96_000);
+
+
+ if (spec->in_enum_val == REAR_LINE_IN)
+ tmp = FLOAT_ZERO;
+ else {
+ if (ca0132_quirk(spec) == QUIRK_SBZ)
+ tmp = FLOAT_THREE;
+ else
+ tmp = FLOAT_ONE;
+ }
+
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ } else {
+ codec_dbg(codec, "%s: on.", __func__);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ chipio_set_conn_rate(codec, 0x0F, SR_16_000);
+
+ if (spec->effects_switch[VOICE_FOCUS - EFFECT_START_NID])
+ tmp = FLOAT_TWO;
+ else
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x05, tmp);
+
+ msleep(20);
+ chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, val);
+ }
+
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+
+ return 1;
+}
+
+/*
+ * Select the active microphone.
+ * If autodetect is enabled, mic will be selected based on jack detection.
+ * If jack inserted, ext.mic will be selected, else built-in mic
+ * If autodetect is disabled, mic will be selected based on selection.
+ */
+static int ca0132_select_mic(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int jack_present;
+ int auto_jack;
+
+ codec_dbg(codec, "ca0132_select_mic\n");
+
+ snd_hda_power_up_pm(codec);
+
+ auto_jack = spec->vnode_lswitch[VNID_AMIC1_ASEL - VNODE_START_NID];
+
+ if (auto_jack)
+ jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_amic1);
+ else
+ jack_present =
+ spec->vnode_lswitch[VNID_AMIC1_SEL - VNODE_START_NID];
+
+ if (jack_present)
+ spec->cur_mic_type = LINE_MIC_IN;
+ else
+ spec->cur_mic_type = DIGITAL_MIC;
+
+ if (spec->cur_mic_type == DIGITAL_MIC) {
+ /* enable digital Mic */
+ chipio_set_conn_rate(codec, MEM_CONNID_DMIC, SR_32_000);
+ ca0132_set_dmic(codec, 1);
+ ca0132_mic_boost_set(codec, 0);
+ /* set voice focus */
+ ca0132_effects_set(codec, VOICE_FOCUS,
+ spec->effects_switch
+ [VOICE_FOCUS - EFFECT_START_NID]);
+ } else {
+ /* disable digital Mic */
+ chipio_set_conn_rate(codec, MEM_CONNID_DMIC, SR_96_000);
+ ca0132_set_dmic(codec, 0);
+ ca0132_mic_boost_set(codec, spec->cur_mic_boost);
+ /* disable voice focus */
+ ca0132_effects_set(codec, VOICE_FOCUS, 0);
+ }
+
+ snd_hda_power_down_pm(codec);
+
+ return 0;
+}
+
+/*
+ * Select the active input.
+ * Mic detection isn't used, because it's kind of pointless on the SBZ.
+ * The front mic has no jack-detection, so the only way to switch to it
+ * is to do it manually in alsamixer.
+ */
+static int ca0132_alt_select_in(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+
+ codec_dbg(codec, "%s\n", __func__);
+
+ snd_hda_power_up_pm(codec);
+
+ chipio_set_stream_control(codec, 0x03, 0);
+ chipio_set_stream_control(codec, 0x04, 0);
+
+ spec->cur_mic_type = spec->in_enum_val;
+
+ switch (spec->cur_mic_type) {
+ case REAR_MIC:
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_R3D:
+ ca0113_mmio_gpio_set(codec, 0, false);
+ tmp = FLOAT_THREE;
+ break;
+ case QUIRK_ZXR:
+ tmp = FLOAT_THREE;
+ break;
+ case QUIRK_R3DI:
+ r3di_gpio_mic_set(codec, R3DI_REAR_MIC);
+ tmp = FLOAT_ONE;
+ break;
+ case QUIRK_AE5:
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00);
+ tmp = FLOAT_THREE;
+ break;
+ case QUIRK_AE7:
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00);
+ tmp = FLOAT_THREE;
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN2,
+ SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2,
+ SR_96_000);
+ dspio_set_uint_param(codec, 0x80, 0x01, FLOAT_ZERO);
+ break;
+ default:
+ tmp = FLOAT_ONE;
+ break;
+ }
+
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ chipio_set_conn_rate(codec, 0x0F, SR_96_000);
+
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ chipio_write(codec, 0x18B098, 0x0000000C);
+ chipio_write(codec, 0x18B09C, 0x0000000C);
+ break;
+ case QUIRK_ZXR:
+ chipio_write(codec, 0x18B098, 0x0000000C);
+ chipio_write(codec, 0x18B09C, 0x000000CC);
+ break;
+ case QUIRK_AE5:
+ chipio_write(codec, 0x18B098, 0x0000000C);
+ chipio_write(codec, 0x18B09C, 0x0000004C);
+ break;
+ default:
+ break;
+ }
+ ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val);
+ break;
+ case REAR_LINE_IN:
+ ca0132_mic_boost_set(codec, 0);
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_R3D:
+ ca0113_mmio_gpio_set(codec, 0, false);
+ break;
+ case QUIRK_R3DI:
+ r3di_gpio_mic_set(codec, R3DI_REAR_MIC);
+ break;
+ case QUIRK_AE5:
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00);
+ break;
+ case QUIRK_AE7:
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x3f);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN2,
+ SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2,
+ SR_96_000);
+ dspio_set_uint_param(codec, 0x80, 0x01, FLOAT_ZERO);
+ break;
+ default:
+ break;
+ }
+
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ chipio_set_conn_rate(codec, 0x0F, SR_96_000);
+
+ if (ca0132_quirk(spec) == QUIRK_AE7)
+ tmp = FLOAT_THREE;
+ else
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_AE5:
+ chipio_write(codec, 0x18B098, 0x00000000);
+ chipio_write(codec, 0x18B09C, 0x00000000);
+ break;
+ default:
+ break;
+ }
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+ break;
+ case FRONT_MIC:
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_R3D:
+ ca0113_mmio_gpio_set(codec, 0, true);
+ ca0113_mmio_gpio_set(codec, 5, false);
+ tmp = FLOAT_THREE;
+ break;
+ case QUIRK_R3DI:
+ r3di_gpio_mic_set(codec, R3DI_FRONT_MIC);
+ tmp = FLOAT_ONE;
+ break;
+ case QUIRK_AE5:
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x3f);
+ tmp = FLOAT_THREE;
+ break;
+ default:
+ tmp = FLOAT_ONE;
+ break;
+ }
+
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ chipio_set_conn_rate(codec, 0x0F, SR_96_000);
+
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ chipio_write(codec, 0x18B098, 0x0000000C);
+ chipio_write(codec, 0x18B09C, 0x000000CC);
+ break;
+ case QUIRK_AE5:
+ chipio_write(codec, 0x18B098, 0x0000000C);
+ chipio_write(codec, 0x18B09C, 0x0000004C);
+ break;
+ default:
+ break;
+ }
+ ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val);
+ break;
+ }
+ ca0132_cvoice_switch_set(codec);
+
+ snd_hda_power_down_pm(codec);
+ return 0;
+}
+
+/*
+ * Check if VNODE settings take effect immediately.
+ */
+static bool ca0132_is_vnode_effective(struct hda_codec *codec,
+ hda_nid_t vnid,
+ hda_nid_t *shared_nid)
+{
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid;
+
+ switch (vnid) {
+ case VNID_SPK:
+ nid = spec->shared_out_nid;
+ break;
+ case VNID_MIC:
+ nid = spec->shared_mic_nid;
+ break;
+ default:
+ return false;
+ }
+
+ if (shared_nid)
+ *shared_nid = nid;
+
+ return true;
+}
+
+/*
+* The following functions are control change helpers.
+* They return 0 if no changed. Return 1 if changed.
+*/
+static int ca0132_voicefx_set(struct hda_codec *codec, int enable)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+
+ /* based on CrystalVoice state to enable VoiceFX. */
+ if (enable) {
+ tmp = spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] ?
+ FLOAT_ONE : FLOAT_ZERO;
+ } else {
+ tmp = FLOAT_ZERO;
+ }
+
+ dspio_set_uint_param(codec, ca0132_voicefx.mid,
+ ca0132_voicefx.reqs[0], tmp);
+
+ return 1;
+}
+
+/*
+ * Set the effects parameters
+ */
+static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int on, tmp, channel_cfg;
+ int num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT;
+ int err = 0;
+ int idx = nid - EFFECT_START_NID;
+
+ if ((idx < 0) || (idx >= num_fx))
+ return 0; /* no changed */
+
+ /* for out effect, qualify with PE */
+ if ((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) {
+ /* if PE if off, turn off out effects. */
+ if (!spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID])
+ val = 0;
+ if (spec->cur_out_type == SPEAKER_OUT && nid == X_BASS) {
+ channel_cfg = spec->channel_cfg_val;
+ if (channel_cfg != SPEAKER_CHANNELS_2_0 &&
+ channel_cfg != SPEAKER_CHANNELS_4_0)
+ val = 0;
+ }
+ }
+
+ /* for in effect, qualify with CrystalVoice */
+ if ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID)) {
+ /* if CrystalVoice if off, turn off in effects. */
+ if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID])
+ val = 0;
+
+ /* Voice Focus applies to 2-ch Mic, Digital Mic */
+ if ((nid == VOICE_FOCUS) && (spec->cur_mic_type != DIGITAL_MIC))
+ val = 0;
+
+ /* If Voice Focus on SBZ, set to two channel. */
+ if ((nid == VOICE_FOCUS) && ca0132_use_pci_mmio(spec)
+ && (spec->cur_mic_type != REAR_LINE_IN)) {
+ if (spec->effects_switch[CRYSTAL_VOICE -
+ EFFECT_START_NID]) {
+
+ if (spec->effects_switch[VOICE_FOCUS -
+ EFFECT_START_NID]) {
+ tmp = FLOAT_TWO;
+ val = 1;
+ } else
+ tmp = FLOAT_ONE;
+
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+ }
+ }
+ /*
+ * For SBZ noise reduction, there's an extra command
+ * to module ID 0x47. No clue why.
+ */
+ if ((nid == NOISE_REDUCTION) && ca0132_use_pci_mmio(spec)
+ && (spec->cur_mic_type != REAR_LINE_IN)) {
+ if (spec->effects_switch[CRYSTAL_VOICE -
+ EFFECT_START_NID]) {
+ if (spec->effects_switch[NOISE_REDUCTION -
+ EFFECT_START_NID])
+ tmp = FLOAT_ONE;
+ else
+ tmp = FLOAT_ZERO;
+ } else
+ tmp = FLOAT_ZERO;
+
+ dspio_set_uint_param(codec, 0x47, 0x00, tmp);
+ }
+
+ /* If rear line in disable effects. */
+ if (ca0132_use_alt_functions(spec) &&
+ spec->in_enum_val == REAR_LINE_IN)
+ val = 0;
+ }
+
+ codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n",
+ nid, val);
+
+ on = (val == 0) ? FLOAT_ZERO : FLOAT_ONE;
+ err = dspio_set_uint_param(codec, ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[0], on);
+
+ if (err < 0)
+ return 0; /* no changed */
+
+ return 1;
+}
+
+/*
+ * Turn on/off Playback Enhancements
+ */
+static int ca0132_pe_switch_set(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid;
+ int i, ret = 0;
+
+ codec_dbg(codec, "ca0132_pe_switch_set: val=%ld\n",
+ spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]);
+
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_select_out(codec);
+
+ i = OUT_EFFECT_START_NID - EFFECT_START_NID;
+ nid = OUT_EFFECT_START_NID;
+ /* PE affects all out effects */
+ for (; nid < OUT_EFFECT_END_NID; nid++, i++)
+ ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]);
+
+ return ret;
+}
+
+/* Check if Mic1 is streaming, if so, stop streaming */
+static int stop_mic1(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int oldval = snd_hda_codec_read(codec, spec->adcs[0], 0,
+ AC_VERB_GET_CONV, 0);
+ if (oldval != 0)
+ snd_hda_codec_write(codec, spec->adcs[0], 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ 0);
+ return oldval;
+}
+
+/* Resume Mic1 streaming if it was stopped. */
+static void resume_mic1(struct hda_codec *codec, unsigned int oldval)
+{
+ struct ca0132_spec *spec = codec->spec;
+ /* Restore the previous stream and channel */
+ if (oldval != 0)
+ snd_hda_codec_write(codec, spec->adcs[0], 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ oldval);
+}
+
+/*
+ * Turn on/off CrystalVoice
+ */
+static int ca0132_cvoice_switch_set(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid;
+ int i, ret = 0;
+ unsigned int oldval;
+
+ codec_dbg(codec, "ca0132_cvoice_switch_set: val=%ld\n",
+ spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]);
+
+ i = IN_EFFECT_START_NID - EFFECT_START_NID;
+ nid = IN_EFFECT_START_NID;
+ /* CrystalVoice affects all in effects */
+ for (; nid < IN_EFFECT_END_NID; nid++, i++)
+ ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]);
+
+ /* including VoiceFX */
+ ret |= ca0132_voicefx_set(codec, (spec->voicefx_val ? 1 : 0));
+
+ /* set correct vipsource */
+ oldval = stop_mic1(codec);
+ if (ca0132_use_alt_functions(spec))
+ ret |= ca0132_alt_set_vipsource(codec, 1);
+ else
+ ret |= ca0132_set_vipsource(codec, 1);
+ resume_mic1(codec, oldval);
+ return ret;
+}
+
+static int ca0132_mic_boost_set(struct hda_codec *codec, long val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int ret = 0;
+
+ if (val) /* on */
+ ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0,
+ HDA_INPUT, 0, HDA_AMP_VOLMASK, 3);
+ else /* off */
+ ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0,
+ HDA_INPUT, 0, HDA_AMP_VOLMASK, 0);
+
+ return ret;
+}
+
+static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int ret = 0;
+
+ ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0,
+ HDA_INPUT, 0, HDA_AMP_VOLMASK, val);
+ return ret;
+}
+
+static int ae5_headphone_gain_set(struct hda_codec *codec, long val)
+{
+ unsigned int i;
+
+ for (i = 0; i < 4; i++)
+ ca0113_mmio_command_set(codec, 0x48, 0x11 + i,
+ ae5_headphone_gain_presets[val].vals[i]);
+ return 0;
+}
+
+/*
+ * gpio pin 1 is a relay that switches on/off, apparently setting the headphone
+ * amplifier to handle a 600 ohm load.
+ */
+static int zxr_headphone_gain_set(struct hda_codec *codec, long val)
+{
+ ca0113_mmio_gpio_set(codec, 1, val);
+
+ return 0;
+}
+
+static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ hda_nid_t shared_nid = 0;
+ bool effective;
+ int ret = 0;
+ struct ca0132_spec *spec = codec->spec;
+ int auto_jack;
+
+ if (nid == VNID_HP_SEL) {
+ auto_jack =
+ spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID];
+ if (!auto_jack) {
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_select_out(codec);
+ else
+ ca0132_select_out(codec);
+ }
+ return 1;
+ }
+
+ if (nid == VNID_AMIC1_SEL) {
+ auto_jack =
+ spec->vnode_lswitch[VNID_AMIC1_ASEL - VNODE_START_NID];
+ if (!auto_jack)
+ ca0132_select_mic(codec);
+ return 1;
+ }
+
+ if (nid == VNID_HP_ASEL) {
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_select_out(codec);
+ else
+ ca0132_select_out(codec);
+ return 1;
+ }
+
+ if (nid == VNID_AMIC1_ASEL) {
+ ca0132_select_mic(codec);
+ return 1;
+ }
+
+ /* if effective conditions, then update hw immediately. */
+ effective = ca0132_is_vnode_effective(codec, nid, &shared_nid);
+ if (effective) {
+ int dir = get_amp_direction(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ unsigned long pval;
+
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(shared_nid, ch,
+ 0, dir);
+ ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ }
+
+ return ret;
+}
+/* End of control change helpers. */
+
+static void ca0132_alt_bass_redirection_xover_set(struct hda_codec *codec,
+ long idx)
+{
+ snd_hda_power_up(codec);
+
+ dspio_set_param(codec, 0x96, 0x20, SPEAKER_BASS_REDIRECT_XOVER_FREQ,
+ &(float_xbass_xover_lookup[idx]), sizeof(unsigned int));
+
+ snd_hda_power_down(codec);
+}
+
+/*
+ * Below I've added controls to mess with the effect levels, I've only enabled
+ * them on the Sound Blaster Z, but they would probably also work on the
+ * Chromebook. I figured they were probably tuned specifically for it, and left
+ * out for a reason.
+ */
+
+/* Sets DSP effect level from the sliders above the controls */
+
+static int ca0132_alt_slider_ctl_set(struct hda_codec *codec, hda_nid_t nid,
+ const unsigned int *lookup, int idx)
+{
+ int i = 0;
+ unsigned int y;
+ /*
+ * For X_BASS, req 2 is actually crossover freq instead of
+ * effect level
+ */
+ if (nid == X_BASS)
+ y = 2;
+ else
+ y = 1;
+
+ snd_hda_power_up(codec);
+ if (nid == XBASS_XOVER) {
+ for (i = 0; i < OUT_EFFECTS_COUNT; i++)
+ if (ca0132_effects[i].nid == X_BASS)
+ break;
+
+ dspio_set_param(codec, ca0132_effects[i].mid, 0x20,
+ ca0132_effects[i].reqs[1],
+ &(lookup[idx - 1]), sizeof(unsigned int));
+ } else {
+ /* Find the actual effect structure */
+ for (i = 0; i < OUT_EFFECTS_COUNT; i++)
+ if (nid == ca0132_effects[i].nid)
+ break;
+
+ dspio_set_param(codec, ca0132_effects[i].mid, 0x20,
+ ca0132_effects[i].reqs[y],
+ &(lookup[idx]), sizeof(unsigned int));
+ }
+
+ snd_hda_power_down(codec);
+
+ return 0;
+}
+
+static int ca0132_alt_xbass_xover_slider_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ long *valp = ucontrol->value.integer.value;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+
+ if (nid == BASS_REDIRECTION_XOVER)
+ *valp = spec->bass_redirect_xover_freq;
+ else
+ *valp = spec->xbass_xover_freq;
+
+ return 0;
+}
+
+static int ca0132_alt_slider_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int idx = nid - OUT_EFFECT_START_NID;
+
+ *valp = spec->fx_ctl_val[idx];
+ return 0;
+}
+
+/*
+ * The X-bass crossover starts at 10hz, so the min is 1. The
+ * frequency is set in multiples of 10.
+ */
+static int ca0132_alt_xbass_xover_slider_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 = 1;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int ca0132_alt_effect_slider_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int ca0132_alt_xbass_xover_slider_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ long *cur_val;
+ int idx;
+
+ if (nid == BASS_REDIRECTION_XOVER)
+ cur_val = &spec->bass_redirect_xover_freq;
+ else
+ cur_val = &spec->xbass_xover_freq;
+
+ /* any change? */
+ if (*cur_val == *valp)
+ return 0;
+
+ *cur_val = *valp;
+
+ idx = *valp;
+ if (nid == BASS_REDIRECTION_XOVER)
+ ca0132_alt_bass_redirection_xover_set(codec, *cur_val);
+ else
+ ca0132_alt_slider_ctl_set(codec, nid, float_xbass_xover_lookup, idx);
+
+ return 0;
+}
+
+static int ca0132_alt_effect_slider_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int idx;
+
+ idx = nid - EFFECT_START_NID;
+ /* any change? */
+ if (spec->fx_ctl_val[idx] == *valp)
+ return 0;
+
+ spec->fx_ctl_val[idx] = *valp;
+
+ idx = *valp;
+ ca0132_alt_slider_ctl_set(codec, nid, float_zero_to_one_lookup, idx);
+
+ return 0;
+}
+
+
+/*
+ * Mic Boost Enum for alternative ca0132 codecs. I didn't like that the original
+ * only has off or full 30 dB, and didn't like making a volume slider that has
+ * traditional 0-100 in alsamixer that goes in big steps. I like enum better.
+ */
+#define MIC_BOOST_NUM_OF_STEPS 4
+#define MIC_BOOST_ENUM_MAX_STRLEN 10
+
+static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ char *sfx = "dB";
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS;
+ if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS)
+ uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1;
+ sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx);
+ strcpy(uinfo->value.enumerated.name, namestr);
+ return 0;
+}
+
+static int ca0132_alt_mic_boost_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->mic_boost_enum_val;
+ return 0;
+}
+
+static int ca0132_alt_mic_boost_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = MIC_BOOST_NUM_OF_STEPS;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ca0132_alt_mic_boost: boost=%d\n",
+ sel);
+
+ spec->mic_boost_enum_val = sel;
+
+ if (spec->in_enum_val != REAR_LINE_IN)
+ ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val);
+
+ return 1;
+}
+
+/*
+ * Sound BlasterX AE-5 Headphone Gain Controls.
+ */
+#define AE5_HEADPHONE_GAIN_MAX 3
+static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ char *sfx = " Ohms)";
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX;
+ if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX)
+ uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1;
+ sprintf(namestr, "%s %s",
+ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name,
+ sfx);
+ strcpy(uinfo->value.enumerated.name, namestr);
+ return 0;
+}
+
+static int ae5_headphone_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->ae5_headphone_gain_val;
+ return 0;
+}
+
+static int ae5_headphone_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = AE5_HEADPHONE_GAIN_MAX;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ae5_headphone_gain: boost=%d\n",
+ sel);
+
+ spec->ae5_headphone_gain_val = sel;
+
+ if (spec->out_enum_val == HEADPHONE_OUT)
+ ae5_headphone_gain_set(codec, spec->ae5_headphone_gain_val);
+
+ return 1;
+}
+
+/*
+ * Sound BlasterX AE-5 sound filter enumerated control.
+ */
+#define AE5_SOUND_FILTER_MAX 3
+
+static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX;
+ if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX)
+ uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1;
+ sprintf(namestr, "%s",
+ ae5_filter_presets[uinfo->value.enumerated.item].name);
+ strcpy(uinfo->value.enumerated.name, namestr);
+ return 0;
+}
+
+static int ae5_sound_filter_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->ae5_filter_val;
+ return 0;
+}
+
+static int ae5_sound_filter_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = AE5_SOUND_FILTER_MAX;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ae5_sound_filter: %s\n",
+ ae5_filter_presets[sel].name);
+
+ spec->ae5_filter_val = sel;
+
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07,
+ ae5_filter_presets[sel].val);
+
+ return 1;
+}
+
+/*
+ * Input Select Control for alternative ca0132 codecs. This exists because
+ * front microphone has no auto-detect, and we need a way to set the rear
+ * as line-in
+ */
+static int ca0132_alt_input_source_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = IN_SRC_NUM_OF_INPUTS;
+ if (uinfo->value.enumerated.item >= IN_SRC_NUM_OF_INPUTS)
+ uinfo->value.enumerated.item = IN_SRC_NUM_OF_INPUTS - 1;
+ strcpy(uinfo->value.enumerated.name,
+ in_src_str[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int ca0132_alt_input_source_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->in_enum_val;
+ return 0;
+}
+
+static int ca0132_alt_input_source_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = IN_SRC_NUM_OF_INPUTS;
+
+ /*
+ * The AE-7 has no front microphone, so limit items to 2: rear mic and
+ * line-in.
+ */
+ if (ca0132_quirk(spec) == QUIRK_AE7)
+ items = 2;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ca0132_alt_input_select: sel=%d, preset=%s\n",
+ sel, in_src_str[sel]);
+
+ spec->in_enum_val = sel;
+
+ ca0132_alt_select_in(codec);
+
+ return 1;
+}
+
+/* Sound Blaster Z Output Select Control */
+static int ca0132_alt_output_select_get_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = NUM_OF_OUTPUTS;
+ if (uinfo->value.enumerated.item >= NUM_OF_OUTPUTS)
+ uinfo->value.enumerated.item = NUM_OF_OUTPUTS - 1;
+ strcpy(uinfo->value.enumerated.name,
+ out_type_str[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int ca0132_alt_output_select_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->out_enum_val;
+ return 0;
+}
+
+static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = NUM_OF_OUTPUTS;
+ unsigned int auto_jack;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ca0132_alt_output_select: sel=%d, preset=%s\n",
+ sel, out_type_str[sel]);
+
+ spec->out_enum_val = sel;
+
+ auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID];
+
+ if (!auto_jack)
+ ca0132_alt_select_out(codec);
+
+ return 1;
+}
+
+/* Select surround output type: 2.1, 4.0, 4.1, or 5.1. */
+static int ca0132_alt_speaker_channel_cfg_get_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int items = SPEAKER_CHANNEL_CFG_COUNT;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = items;
+ if (uinfo->value.enumerated.item >= items)
+ uinfo->value.enumerated.item = items - 1;
+ strcpy(uinfo->value.enumerated.name,
+ speaker_channel_cfgs[uinfo->value.enumerated.item].name);
+ return 0;
+}
+
+static int ca0132_alt_speaker_channel_cfg_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->channel_cfg_val;
+ return 0;
+}
+
+static int ca0132_alt_speaker_channel_cfg_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = SPEAKER_CHANNEL_CFG_COUNT;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ca0132_alt_speaker_channels: sel=%d, channels=%s\n",
+ sel, speaker_channel_cfgs[sel].name);
+
+ spec->channel_cfg_val = sel;
+
+ if (spec->out_enum_val == SPEAKER_OUT)
+ ca0132_alt_select_out(codec);
+
+ return 1;
+}
+
+/*
+ * Smart Volume output setting control. Three different settings, Normal,
+ * which takes the value from the smart volume slider. The two others, loud
+ * and night, disregard the slider value and have uneditable values.
+ */
+#define NUM_OF_SVM_SETTINGS 3
+static const char *const out_svm_set_enum_str[3] = {"Normal", "Loud", "Night" };
+
+static int ca0132_alt_svm_setting_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = NUM_OF_SVM_SETTINGS;
+ if (uinfo->value.enumerated.item >= NUM_OF_SVM_SETTINGS)
+ uinfo->value.enumerated.item = NUM_OF_SVM_SETTINGS - 1;
+ strcpy(uinfo->value.enumerated.name,
+ out_svm_set_enum_str[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int ca0132_alt_svm_setting_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->smart_volume_setting;
+ return 0;
+}
+
+static int ca0132_alt_svm_setting_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = NUM_OF_SVM_SETTINGS;
+ unsigned int idx = SMART_VOLUME - EFFECT_START_NID;
+ unsigned int tmp;
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "ca0132_alt_svm_setting: sel=%d, preset=%s\n",
+ sel, out_svm_set_enum_str[sel]);
+
+ spec->smart_volume_setting = sel;
+
+ switch (sel) {
+ case 0:
+ tmp = FLOAT_ZERO;
+ break;
+ case 1:
+ tmp = FLOAT_ONE;
+ break;
+ case 2:
+ tmp = FLOAT_TWO;
+ break;
+ default:
+ tmp = FLOAT_ZERO;
+ break;
+ }
+ /* Req 2 is the Smart Volume Setting req. */
+ dspio_set_uint_param(codec, ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[2], tmp);
+ return 1;
+}
+
+/* Sound Blaster Z EQ preset controls */
+static int ca0132_alt_eq_preset_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int items = ARRAY_SIZE(ca0132_alt_eq_presets);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = items;
+ if (uinfo->value.enumerated.item >= items)
+ uinfo->value.enumerated.item = items - 1;
+ strcpy(uinfo->value.enumerated.name,
+ ca0132_alt_eq_presets[uinfo->value.enumerated.item].name);
+ return 0;
+}
+
+static int ca0132_alt_eq_preset_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->eq_preset_val;
+ return 0;
+}
+
+static int ca0132_alt_eq_preset_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int i, err = 0;
+ int sel = ucontrol->value.enumerated.item[0];
+ unsigned int items = ARRAY_SIZE(ca0132_alt_eq_presets);
+
+ if (sel >= items)
+ return 0;
+
+ codec_dbg(codec, "%s: sel=%d, preset=%s\n", __func__, sel,
+ ca0132_alt_eq_presets[sel].name);
+ /*
+ * Idx 0 is default.
+ * Default needs to qualify with CrystalVoice state.
+ */
+ for (i = 0; i < EQ_PRESET_MAX_PARAM_COUNT; i++) {
+ err = dspio_set_uint_param(codec, ca0132_alt_eq_enum.mid,
+ ca0132_alt_eq_enum.reqs[i],
+ ca0132_alt_eq_presets[sel].vals[i]);
+ if (err < 0)
+ break;
+ }
+
+ if (err >= 0)
+ spec->eq_preset_val = sel;
+
+ return 1;
+}
+
+static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int items = ARRAY_SIZE(ca0132_voicefx_presets);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = items;
+ if (uinfo->value.enumerated.item >= items)
+ uinfo->value.enumerated.item = items - 1;
+ strcpy(uinfo->value.enumerated.name,
+ ca0132_voicefx_presets[uinfo->value.enumerated.item].name);
+ return 0;
+}
+
+static int ca0132_voicefx_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->voicefx_val;
+ return 0;
+}
+
+static int ca0132_voicefx_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ int i, err = 0;
+ int sel = ucontrol->value.enumerated.item[0];
+
+ if (sel >= ARRAY_SIZE(ca0132_voicefx_presets))
+ return 0;
+
+ codec_dbg(codec, "ca0132_voicefx_put: sel=%d, preset=%s\n",
+ sel, ca0132_voicefx_presets[sel].name);
+
+ /*
+ * Idx 0 is default.
+ * Default needs to qualify with CrystalVoice state.
+ */
+ for (i = 0; i < VOICEFX_MAX_PARAM_COUNT; i++) {
+ err = dspio_set_uint_param(codec, ca0132_voicefx.mid,
+ ca0132_voicefx.reqs[i],
+ ca0132_voicefx_presets[sel].vals[i]);
+ if (err < 0)
+ break;
+ }
+
+ if (err >= 0) {
+ spec->voicefx_val = sel;
+ /* enable voice fx */
+ ca0132_voicefx_set(codec, (sel ? 1 : 0));
+ }
+
+ return 1;
+}
+
+static int ca0132_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ /* vnode */
+ if ((nid >= VNODE_START_NID) && (nid < VNODE_END_NID)) {
+ if (ch & 1) {
+ *valp = spec->vnode_lswitch[nid - VNODE_START_NID];
+ valp++;
+ }
+ if (ch & 2) {
+ *valp = spec->vnode_rswitch[nid - VNODE_START_NID];
+ valp++;
+ }
+ return 0;
+ }
+
+ /* effects, include PE and CrystalVoice */
+ if ((nid >= EFFECT_START_NID) && (nid < EFFECT_END_NID)) {
+ *valp = spec->effects_switch[nid - EFFECT_START_NID];
+ return 0;
+ }
+
+ /* mic boost */
+ if (nid == spec->input_pins[0]) {
+ *valp = spec->cur_mic_boost;
+ return 0;
+ }
+
+ if (nid == ZXR_HEADPHONE_GAIN) {
+ *valp = spec->zxr_gain_set;
+ return 0;
+ }
+
+ if (nid == SPEAKER_FULL_RANGE_FRONT || nid == SPEAKER_FULL_RANGE_REAR) {
+ *valp = spec->speaker_range_val[nid - SPEAKER_FULL_RANGE_FRONT];
+ return 0;
+ }
+
+ if (nid == BASS_REDIRECTION) {
+ *valp = spec->bass_redirection_val;
+ return 0;
+ }
+
+ return 0;
+}
+
+static int ca0132_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int changed = 1;
+
+ codec_dbg(codec, "ca0132_switch_put: nid=0x%x, val=%ld\n",
+ nid, *valp);
+
+ snd_hda_power_up(codec);
+ /* vnode */
+ if ((nid >= VNODE_START_NID) && (nid < VNODE_END_NID)) {
+ if (ch & 1) {
+ spec->vnode_lswitch[nid - VNODE_START_NID] = *valp;
+ valp++;
+ }
+ if (ch & 2) {
+ spec->vnode_rswitch[nid - VNODE_START_NID] = *valp;
+ valp++;
+ }
+ changed = ca0132_vnode_switch_set(kcontrol, ucontrol);
+ goto exit;
+ }
+
+ /* PE */
+ if (nid == PLAY_ENHANCEMENT) {
+ spec->effects_switch[nid - EFFECT_START_NID] = *valp;
+ changed = ca0132_pe_switch_set(codec);
+ goto exit;
+ }
+
+ /* CrystalVoice */
+ if (nid == CRYSTAL_VOICE) {
+ spec->effects_switch[nid - EFFECT_START_NID] = *valp;
+ changed = ca0132_cvoice_switch_set(codec);
+ goto exit;
+ }
+
+ /* out and in effects */
+ if (((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) ||
+ ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID))) {
+ spec->effects_switch[nid - EFFECT_START_NID] = *valp;
+ changed = ca0132_effects_set(codec, nid, *valp);
+ goto exit;
+ }
+
+ /* mic boost */
+ if (nid == spec->input_pins[0]) {
+ spec->cur_mic_boost = *valp;
+ if (ca0132_use_alt_functions(spec)) {
+ if (spec->in_enum_val != REAR_LINE_IN)
+ changed = ca0132_mic_boost_set(codec, *valp);
+ } else {
+ /* Mic boost does not apply to Digital Mic */
+ if (spec->cur_mic_type != DIGITAL_MIC)
+ changed = ca0132_mic_boost_set(codec, *valp);
+ }
+
+ goto exit;
+ }
+
+ if (nid == ZXR_HEADPHONE_GAIN) {
+ spec->zxr_gain_set = *valp;
+ if (spec->cur_out_type == HEADPHONE_OUT)
+ changed = zxr_headphone_gain_set(codec, *valp);
+ else
+ changed = 0;
+
+ goto exit;
+ }
+
+ if (nid == SPEAKER_FULL_RANGE_FRONT || nid == SPEAKER_FULL_RANGE_REAR) {
+ spec->speaker_range_val[nid - SPEAKER_FULL_RANGE_FRONT] = *valp;
+ if (spec->cur_out_type == SPEAKER_OUT)
+ ca0132_alt_set_full_range_speaker(codec);
+
+ changed = 0;
+ }
+
+ if (nid == BASS_REDIRECTION) {
+ spec->bass_redirection_val = *valp;
+ if (spec->cur_out_type == SPEAKER_OUT)
+ ca0132_alt_surround_set_bass_redirection(codec, *valp);
+
+ changed = 0;
+ }
+
+exit:
+ snd_hda_power_down(codec);
+ return changed;
+}
+
+/*
+ * Volume related
+ */
+/*
+ * Sets the internal DSP decibel level to match the DAC for output, and the
+ * ADC for input. Currently only the SBZ sets dsp capture volume level, and
+ * all alternative codecs set DSP playback volume.
+ */
+static void ca0132_alt_dsp_volume_put(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int dsp_dir;
+ unsigned int lookup_val;
+
+ if (nid == VNID_SPK)
+ dsp_dir = DSP_VOL_OUT;
+ else
+ dsp_dir = DSP_VOL_IN;
+
+ lookup_val = spec->vnode_lvol[nid - VNODE_START_NID];
+
+ dspio_set_uint_param(codec,
+ ca0132_alt_vol_ctls[dsp_dir].mid,
+ ca0132_alt_vol_ctls[dsp_dir].reqs[0],
+ float_vol_db_lookup[lookup_val]);
+
+ lookup_val = spec->vnode_rvol[nid - VNODE_START_NID];
+
+ dspio_set_uint_param(codec,
+ ca0132_alt_vol_ctls[dsp_dir].mid,
+ ca0132_alt_vol_ctls[dsp_dir].reqs[1],
+ float_vol_db_lookup[lookup_val]);
+
+ dspio_set_uint_param(codec,
+ ca0132_alt_vol_ctls[dsp_dir].mid,
+ ca0132_alt_vol_ctls[dsp_dir].reqs[2], FLOAT_ZERO);
+}
+
+static int ca0132_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ unsigned long pval;
+ int err;
+
+ switch (nid) {
+ case VNID_SPK:
+ /* follow shared_out info */
+ nid = spec->shared_out_nid;
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir);
+ err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ break;
+ case VNID_MIC:
+ /* follow shared_mic info */
+ nid = spec->shared_mic_nid;
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir);
+ err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ break;
+ default:
+ err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo);
+ }
+ return err;
+}
+
+static int ca0132_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ /* store the left and right volume */
+ if (ch & 1) {
+ *valp = spec->vnode_lvol[nid - VNODE_START_NID];
+ valp++;
+ }
+ if (ch & 2) {
+ *valp = spec->vnode_rvol[nid - VNODE_START_NID];
+ valp++;
+ }
+ return 0;
+}
+
+static int ca0132_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ hda_nid_t shared_nid = 0;
+ bool effective;
+ int changed = 1;
+
+ /* store the left and right volume */
+ if (ch & 1) {
+ spec->vnode_lvol[nid - VNODE_START_NID] = *valp;
+ valp++;
+ }
+ if (ch & 2) {
+ spec->vnode_rvol[nid - VNODE_START_NID] = *valp;
+ valp++;
+ }
+
+ /* if effective conditions, then update hw immediately. */
+ effective = ca0132_is_vnode_effective(codec, nid, &shared_nid);
+ if (effective) {
+ int dir = get_amp_direction(kcontrol);
+ unsigned long pval;
+
+ snd_hda_power_up(codec);
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(shared_nid, ch,
+ 0, dir);
+ changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ snd_hda_power_down(codec);
+ }
+
+ return changed;
+}
+
+/*
+ * This function is the same as the one above, because using an if statement
+ * inside of the above volume control for the DSP volume would cause too much
+ * lag. This is a lot more smooth.
+ */
+static int ca0132_alt_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ hda_nid_t vnid = 0;
+ int changed;
+
+ switch (nid) {
+ case 0x02:
+ vnid = VNID_SPK;
+ break;
+ case 0x07:
+ vnid = VNID_MIC;
+ break;
+ }
+
+ /* store the left and right volume */
+ if (ch & 1) {
+ spec->vnode_lvol[vnid - VNODE_START_NID] = *valp;
+ valp++;
+ }
+ if (ch & 2) {
+ spec->vnode_rvol[vnid - VNODE_START_NID] = *valp;
+ valp++;
+ }
+
+ snd_hda_power_up(codec);
+ ca0132_alt_dsp_volume_put(codec, vnid);
+ mutex_lock(&codec->control_mutex);
+ changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol);
+ mutex_unlock(&codec->control_mutex);
+ snd_hda_power_down(codec);
+
+ return changed;
+}
+
+static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *tlv)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ca0132_spec *spec = codec->spec;
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int ch = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ unsigned long pval;
+ int err;
+
+ switch (nid) {
+ case VNID_SPK:
+ /* follow shared_out tlv */
+ nid = spec->shared_out_nid;
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir);
+ err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ break;
+ case VNID_MIC:
+ /* follow shared_mic tlv */
+ nid = spec->shared_mic_nid;
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir);
+ err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv);
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ break;
+ default:
+ err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv);
+ }
+ return err;
+}
+
+/* Add volume slider control for effect level */
+static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid,
+ const char *pfx, int dir)
+{
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int type = dir ? HDA_INPUT : HDA_OUTPUT;
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type);
+
+ sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]);
+
+ knew.tlv.c = NULL;
+
+ switch (nid) {
+ case XBASS_XOVER:
+ knew.info = ca0132_alt_xbass_xover_slider_info;
+ knew.get = ca0132_alt_xbass_xover_slider_ctl_get;
+ knew.put = ca0132_alt_xbass_xover_slider_put;
+ break;
+ default:
+ knew.info = ca0132_alt_effect_slider_info;
+ knew.get = ca0132_alt_slider_ctl_get;
+ knew.put = ca0132_alt_effect_slider_put;
+ knew.private_value =
+ HDA_COMPOSE_AMP_VAL(nid, 1, 0, type);
+ break;
+ }
+
+ return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Added FX: prefix for the alternative codecs, because otherwise the surround
+ * effect would conflict with the Surround sound volume control. Also seems more
+ * clear as to what the switches do. Left alone for others.
+ */
+static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid,
+ const char *pfx, int dir)
+{
+ struct ca0132_spec *spec = codec->spec;
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int type = dir ? HDA_INPUT : HDA_OUTPUT;
+ struct snd_kcontrol_new knew =
+ CA0132_CODEC_MUTE_MONO(namestr, nid, 1, type);
+ /* If using alt_controls, add FX: prefix. But, don't add FX:
+ * prefix to OutFX or InFX enable controls.
+ */
+ if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID))
+ sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]);
+ else
+ sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]);
+
+ return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec));
+}
+
+static int add_voicefx(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO(ca0132_voicefx.name,
+ VOICEFX, 1, 0, HDA_INPUT);
+ knew.info = ca0132_voicefx_info;
+ knew.get = ca0132_voicefx_get;
+ knew.put = ca0132_voicefx_put;
+ return snd_hda_ctl_add(codec, VOICEFX, snd_ctl_new1(&knew, codec));
+}
+
+/* Create the EQ Preset control */
+static int add_ca0132_alt_eq_presets(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO(ca0132_alt_eq_enum.name,
+ EQ_PRESET_ENUM, 1, 0, HDA_OUTPUT);
+ knew.info = ca0132_alt_eq_preset_info;
+ knew.get = ca0132_alt_eq_preset_get;
+ knew.put = ca0132_alt_eq_preset_put;
+ return snd_hda_ctl_add(codec, EQ_PRESET_ENUM,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Add enumerated control for the three different settings of the smart volume
+ * output effect. Normal just uses the slider value, and loud and night are
+ * their own things that ignore that value.
+ */
+static int ca0132_alt_add_svm_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("FX: Smart Volume Setting",
+ SMART_VOLUME_ENUM, 1, 0, HDA_OUTPUT);
+ knew.info = ca0132_alt_svm_setting_info;
+ knew.get = ca0132_alt_svm_setting_get;
+ knew.put = ca0132_alt_svm_setting_put;
+ return snd_hda_ctl_add(codec, SMART_VOLUME_ENUM,
+ snd_ctl_new1(&knew, codec));
+
+}
+
+/*
+ * Create an Output Select enumerated control for codecs with surround
+ * out capabilities.
+ */
+static int ca0132_alt_add_output_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("Output Select",
+ OUTPUT_SOURCE_ENUM, 1, 0, HDA_OUTPUT);
+ knew.info = ca0132_alt_output_select_get_info;
+ knew.get = ca0132_alt_output_select_get;
+ knew.put = ca0132_alt_output_select_put;
+ return snd_hda_ctl_add(codec, OUTPUT_SOURCE_ENUM,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Add a control for selecting channel count on speaker output. Setting this
+ * allows the DSP to do bass redirection and channel upmixing on surround
+ * configurations.
+ */
+static int ca0132_alt_add_speaker_channel_cfg_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("Surround Channel Config",
+ SPEAKER_CHANNEL_CFG_ENUM, 1, 0, HDA_OUTPUT);
+ knew.info = ca0132_alt_speaker_channel_cfg_get_info;
+ knew.get = ca0132_alt_speaker_channel_cfg_get;
+ knew.put = ca0132_alt_speaker_channel_cfg_put;
+ return snd_hda_ctl_add(codec, SPEAKER_CHANNEL_CFG_ENUM,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Full range front stereo and rear surround switches. When these are set to
+ * full range, the lower frequencies from these channels are no longer
+ * redirected to the LFE channel.
+ */
+static int ca0132_alt_add_front_full_range_switch(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ CA0132_CODEC_MUTE_MONO("Full-Range Front Speakers",
+ SPEAKER_FULL_RANGE_FRONT, 1, HDA_OUTPUT);
+
+ return snd_hda_ctl_add(codec, SPEAKER_FULL_RANGE_FRONT,
+ snd_ctl_new1(&knew, codec));
+}
+
+static int ca0132_alt_add_rear_full_range_switch(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ CA0132_CODEC_MUTE_MONO("Full-Range Rear Speakers",
+ SPEAKER_FULL_RANGE_REAR, 1, HDA_OUTPUT);
+
+ return snd_hda_ctl_add(codec, SPEAKER_FULL_RANGE_REAR,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Bass redirection redirects audio below the crossover frequency to the LFE
+ * channel on speakers that are set as not being full-range. On configurations
+ * without an LFE channel, it does nothing. Bass redirection seems to be the
+ * replacement for X-Bass on configurations with an LFE channel.
+ */
+static int ca0132_alt_add_bass_redirection_crossover(struct hda_codec *codec)
+{
+ const char *namestr = "Bass Redirection Crossover";
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_VOLUME_MONO(namestr, BASS_REDIRECTION_XOVER, 1, 0,
+ HDA_OUTPUT);
+
+ knew.tlv.c = NULL;
+ knew.info = ca0132_alt_xbass_xover_slider_info;
+ knew.get = ca0132_alt_xbass_xover_slider_ctl_get;
+ knew.put = ca0132_alt_xbass_xover_slider_put;
+
+ return snd_hda_ctl_add(codec, BASS_REDIRECTION_XOVER,
+ snd_ctl_new1(&knew, codec));
+}
+
+static int ca0132_alt_add_bass_redirection_switch(struct hda_codec *codec)
+{
+ const char *namestr = "Bass Redirection";
+ struct snd_kcontrol_new knew =
+ CA0132_CODEC_MUTE_MONO(namestr, BASS_REDIRECTION, 1,
+ HDA_OUTPUT);
+
+ return snd_hda_ctl_add(codec, BASS_REDIRECTION,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Create an Input Source enumerated control for the alternate ca0132 codecs
+ * because the front microphone has no auto-detect, and Line-in has to be set
+ * somehow.
+ */
+static int ca0132_alt_add_input_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("Input Source",
+ INPUT_SOURCE_ENUM, 1, 0, HDA_INPUT);
+ knew.info = ca0132_alt_input_source_info;
+ knew.get = ca0132_alt_input_source_get;
+ knew.put = ca0132_alt_input_source_put;
+ return snd_hda_ctl_add(codec, INPUT_SOURCE_ENUM,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Add mic boost enumerated control. Switches through 0dB to 30dB. This adds
+ * more control than the original mic boost, which is either full 30dB or off.
+ */
+static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("Mic Boost Capture Switch",
+ MIC_BOOST_ENUM, 1, 0, HDA_INPUT);
+ knew.info = ca0132_alt_mic_boost_info;
+ knew.get = ca0132_alt_mic_boost_get;
+ knew.put = ca0132_alt_mic_boost_put;
+ return snd_hda_ctl_add(codec, MIC_BOOST_ENUM,
+ snd_ctl_new1(&knew, codec));
+
+}
+
+/*
+ * Add headphone gain enumerated control for the AE-5. This switches between
+ * three modes, low, medium, and high. When non-headphone outputs are selected,
+ * it is automatically set to high. This is the same behavior as Windows.
+ */
+static int ae5_add_headphone_gain_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("AE-5: Headphone Gain",
+ AE5_HEADPHONE_GAIN_ENUM, 1, 0, HDA_OUTPUT);
+ knew.info = ae5_headphone_gain_info;
+ knew.get = ae5_headphone_gain_get;
+ knew.put = ae5_headphone_gain_put;
+ return snd_hda_ctl_add(codec, AE5_HEADPHONE_GAIN_ENUM,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Add sound filter enumerated control for the AE-5. This adds three different
+ * settings: Slow Roll Off, Minimum Phase, and Fast Roll Off. From what I've
+ * read into it, it changes the DAC's interpolation filter.
+ */
+static int ae5_add_sound_filter_enum(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ HDA_CODEC_MUTE_MONO("AE-5: Sound Filter",
+ AE5_SOUND_FILTER_ENUM, 1, 0, HDA_OUTPUT);
+ knew.info = ae5_sound_filter_info;
+ knew.get = ae5_sound_filter_get;
+ knew.put = ae5_sound_filter_put;
+ return snd_hda_ctl_add(codec, AE5_SOUND_FILTER_ENUM,
+ snd_ctl_new1(&knew, codec));
+}
+
+static int zxr_add_headphone_gain_switch(struct hda_codec *codec)
+{
+ struct snd_kcontrol_new knew =
+ CA0132_CODEC_MUTE_MONO("ZxR: 600 Ohm Gain",
+ ZXR_HEADPHONE_GAIN, 1, HDA_OUTPUT);
+
+ return snd_hda_ctl_add(codec, ZXR_HEADPHONE_GAIN,
+ snd_ctl_new1(&knew, codec));
+}
+
+/*
+ * Need to create follower controls for the alternate codecs that have surround
+ * capabilities.
+ */
+static const char * const ca0132_alt_follower_pfxs[] = {
+ "Front", "Surround", "Center", "LFE", NULL,
+};
+
+/*
+ * Also need special channel map, because the default one is incorrect.
+ * I think this has to do with the pin for rear surround being 0x11,
+ * and the center/lfe being 0x10. Usually the pin order is the opposite.
+ */
+static const struct snd_pcm_chmap_elem ca0132_alt_chmaps[] = {
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+ { .channels = 4,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+ { .channels = 6,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+ SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+ { }
+};
+
+/* Add the correct chmap for streams with 6 channels. */
+static void ca0132_alt_add_chmap_ctls(struct hda_codec *codec)
+{
+ int err = 0;
+ struct hda_pcm *pcm;
+
+ list_for_each_entry(pcm, &codec->pcm_list_head, list) {
+ struct hda_pcm_stream *hinfo =
+ &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK];
+ struct snd_pcm_chmap *chmap;
+ const struct snd_pcm_chmap_elem *elem;
+
+ elem = ca0132_alt_chmaps;
+ if (hinfo->channels_max == 6) {
+ err = snd_pcm_add_chmap_ctls(pcm->pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ elem, hinfo->channels_max, 0, &chmap);
+ if (err < 0)
+ codec_dbg(codec, "snd_pcm_add_chmap_ctls failed!");
+ }
+ }
+}
+
+/*
+ * When changing Node IDs for Mixer Controls below, make sure to update
+ * Node IDs in ca0132_config() as well.
+ */
+static const struct snd_kcontrol_new ca0132_mixer[] = {
+ CA0132_CODEC_VOL("Master Playback Volume", VNID_SPK, HDA_OUTPUT),
+ CA0132_CODEC_MUTE("Master Playback Switch", VNID_SPK, HDA_OUTPUT),
+ CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT),
+ CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT),
+ HDA_CODEC_VOLUME("Analog-Mic2 Capture Volume", 0x08, 0, HDA_INPUT),
+ HDA_CODEC_MUTE("Analog-Mic2 Capture Switch", 0x08, 0, HDA_INPUT),
+ HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT),
+ HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT),
+ CA0132_CODEC_MUTE_MONO("Mic1-Boost (30dB) Capture Switch",
+ 0x12, 1, HDA_INPUT),
+ CA0132_CODEC_MUTE_MONO("HP/Speaker Playback Switch",
+ VNID_HP_SEL, 1, HDA_OUTPUT),
+ CA0132_CODEC_MUTE_MONO("AMic1/DMic Capture Switch",
+ VNID_AMIC1_SEL, 1, HDA_INPUT),
+ CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch",
+ VNID_HP_ASEL, 1, HDA_OUTPUT),
+ CA0132_CODEC_MUTE_MONO("AMic1/DMic Auto Detect Capture Switch",
+ VNID_AMIC1_ASEL, 1, HDA_INPUT),
+ { } /* end */
+};
+
+/*
+ * Desktop specific control mixer. Removes auto-detect for mic, and adds
+ * surround controls. Also sets both the Front Playback and Capture Volume
+ * controls to alt so they set the DSP's decibel level.
+ */
+static const struct snd_kcontrol_new desktop_mixer[] = {
+ CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT),
+ CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT),
+ CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT),
+ CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT),
+ HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT),
+ HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT),
+ CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch",
+ VNID_HP_ASEL, 1, HDA_OUTPUT),
+ { } /* end */
+};
+
+/*
+ * Same as the Sound Blaster Z, except doesn't use the alt volume for capture
+ * because it doesn't set decibel levels for the DSP for capture.
+ */
+static const struct snd_kcontrol_new r3di_mixer[] = {
+ CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT),
+ CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT),
+ CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT),
+ CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT),
+ HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT),
+ HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT),
+ CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch",
+ VNID_HP_ASEL, 1, HDA_OUTPUT),
+ { } /* end */
+};
+
+static int ca0132_build_controls(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int i, num_fx, num_sliders;
+ int err = 0;
+
+ /* Add Mixer controls */
+ for (i = 0; i < spec->num_mixers; i++) {
+ err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+ if (err < 0)
+ return err;
+ }
+ /* Setup vmaster with surround followers for desktop ca0132 devices */
+ if (ca0132_use_alt_functions(spec)) {
+ snd_hda_set_vmaster_tlv(codec, spec->dacs[0], HDA_OUTPUT,
+ spec->tlv);
+ snd_hda_add_vmaster(codec, "Master Playback Volume",
+ spec->tlv, ca0132_alt_follower_pfxs,
+ "Playback Volume");
+ err = __snd_hda_add_vmaster(codec, "Master Playback Switch",
+ NULL, ca0132_alt_follower_pfxs,
+ "Playback Switch",
+ true, &spec->vmaster_mute.sw_kctl);
+ if (err < 0)
+ return err;
+ }
+
+ /* Add in and out effects controls.
+ * VoiceFX, PE and CrystalVoice are added separately.
+ */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT;
+ for (i = 0; i < num_fx; i++) {
+ /* Desktop cards break if Echo Cancellation is used. */
+ if (ca0132_use_pci_mmio(spec)) {
+ if (i == (ECHO_CANCELLATION - IN_EFFECT_START_NID +
+ OUT_EFFECTS_COUNT))
+ continue;
+ }
+
+ err = add_fx_switch(codec, ca0132_effects[i].nid,
+ ca0132_effects[i].name,
+ ca0132_effects[i].direct);
+ if (err < 0)
+ return err;
+ }
+ /*
+ * If codec has use_alt_controls set to true, add effect level sliders,
+ * EQ presets, and Smart Volume presets. Also, change names to add FX
+ * prefix, and change PlayEnhancement and CrystalVoice to match.
+ */
+ if (ca0132_use_alt_controls(spec)) {
+ err = ca0132_alt_add_svm_enum(codec);
+ if (err < 0)
+ return err;
+
+ err = add_ca0132_alt_eq_presets(codec);
+ if (err < 0)
+ return err;
+
+ err = add_fx_switch(codec, PLAY_ENHANCEMENT,
+ "Enable OutFX", 0);
+ if (err < 0)
+ return err;
+
+ err = add_fx_switch(codec, CRYSTAL_VOICE,
+ "Enable InFX", 1);
+ if (err < 0)
+ return err;
+
+ num_sliders = OUT_EFFECTS_COUNT - 1;
+ for (i = 0; i < num_sliders; i++) {
+ err = ca0132_alt_add_effect_slider(codec,
+ ca0132_effects[i].nid,
+ ca0132_effects[i].name,
+ ca0132_effects[i].direct);
+ if (err < 0)
+ return err;
+ }
+
+ err = ca0132_alt_add_effect_slider(codec, XBASS_XOVER,
+ "X-Bass Crossover", EFX_DIR_OUT);
+
+ if (err < 0)
+ return err;
+ } else {
+ err = add_fx_switch(codec, PLAY_ENHANCEMENT,
+ "PlayEnhancement", 0);
+ if (err < 0)
+ return err;
+
+ err = add_fx_switch(codec, CRYSTAL_VOICE,
+ "CrystalVoice", 1);
+ if (err < 0)
+ return err;
+ }
+ err = add_voicefx(codec);
+ if (err < 0)
+ return err;
+
+ /*
+ * If the codec uses alt_functions, you need the enumerated controls
+ * to select the new outputs and inputs, plus add the new mic boost
+ * setting control.
+ */
+ if (ca0132_use_alt_functions(spec)) {
+ err = ca0132_alt_add_output_enum(codec);
+ if (err < 0)
+ return err;
+ err = ca0132_alt_add_speaker_channel_cfg_enum(codec);
+ if (err < 0)
+ return err;
+ err = ca0132_alt_add_front_full_range_switch(codec);
+ if (err < 0)
+ return err;
+ err = ca0132_alt_add_rear_full_range_switch(codec);
+ if (err < 0)
+ return err;
+ err = ca0132_alt_add_bass_redirection_crossover(codec);
+ if (err < 0)
+ return err;
+ err = ca0132_alt_add_bass_redirection_switch(codec);
+ if (err < 0)
+ return err;
+ err = ca0132_alt_add_mic_boost_enum(codec);
+ if (err < 0)
+ return err;
+ /*
+ * ZxR only has microphone input, there is no front panel
+ * header on the card, and aux-in is handled by the DBPro board.
+ */
+ if (ca0132_quirk(spec) != QUIRK_ZXR) {
+ err = ca0132_alt_add_input_enum(codec);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_AE5:
+ case QUIRK_AE7:
+ err = ae5_add_headphone_gain_enum(codec);
+ if (err < 0)
+ return err;
+ err = ae5_add_sound_filter_enum(codec);
+ if (err < 0)
+ return err;
+ break;
+ case QUIRK_ZXR:
+ err = zxr_add_headphone_gain_switch(codec);
+ if (err < 0)
+ return err;
+ break;
+ default:
+ break;
+ }
+
+#ifdef ENABLE_TUNING_CONTROLS
+ add_tuning_ctls(codec);
+#endif
+
+ err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+ if (err < 0)
+ return err;
+
+ if (spec->dig_out) {
+ err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out,
+ spec->dig_out);
+ if (err < 0)
+ return err;
+ err = snd_hda_create_spdif_share_sw(codec, &spec->multiout);
+ if (err < 0)
+ return err;
+ /* spec->multiout.share_spdif = 1; */
+ }
+
+ if (spec->dig_in) {
+ err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in);
+ if (err < 0)
+ return err;
+ }
+
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_add_chmap_ctls(codec);
+
+ return 0;
+}
+
+static int dbpro_build_controls(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int err = 0;
+
+ if (spec->dig_out) {
+ err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out,
+ spec->dig_out);
+ if (err < 0)
+ return err;
+ }
+
+ if (spec->dig_in) {
+ err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * PCM
+ */
+static const struct hda_pcm_stream ca0132_pcm_analog_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 6,
+ .ops = {
+ .prepare = ca0132_playback_pcm_prepare,
+ .cleanup = ca0132_playback_pcm_cleanup,
+ .get_delay = ca0132_playback_pcm_delay,
+ },
+};
+
+static const struct hda_pcm_stream ca0132_pcm_analog_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .ops = {
+ .prepare = ca0132_capture_pcm_prepare,
+ .cleanup = ca0132_capture_pcm_cleanup,
+ .get_delay = ca0132_capture_pcm_delay,
+ },
+};
+
+static const struct hda_pcm_stream ca0132_pcm_digital_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .ops = {
+ .open = ca0132_dig_playback_pcm_open,
+ .close = ca0132_dig_playback_pcm_close,
+ .prepare = ca0132_dig_playback_pcm_prepare,
+ .cleanup = ca0132_dig_playback_pcm_cleanup
+ },
+};
+
+static const struct hda_pcm_stream ca0132_pcm_digital_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+static int ca0132_build_pcms(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct hda_pcm *info;
+
+ info = snd_hda_codec_pcm_new(codec, "CA0132 Analog");
+ if (!info)
+ return -ENOMEM;
+ if (ca0132_use_alt_functions(spec)) {
+ info->own_chmap = true;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap
+ = ca0132_alt_chmaps;
+ }
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0132_pcm_analog_playback;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0];
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+ spec->multiout.max_channels;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0];
+
+ /* With the DSP enabled, desktops don't use this ADC. */
+ if (!ca0132_use_alt_functions(spec)) {
+ info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2");
+ if (!info)
+ return -ENOMEM;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+ ca0132_pcm_analog_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1];
+ }
+
+ info = snd_hda_codec_pcm_new(codec, "CA0132 What U Hear");
+ if (!info)
+ return -ENOMEM;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[2];
+
+ if (!spec->dig_out && !spec->dig_in)
+ return 0;
+
+ info = snd_hda_codec_pcm_new(codec, "CA0132 Digital");
+ if (!info)
+ return -ENOMEM;
+ info->pcm_type = HDA_PCM_TYPE_SPDIF;
+ if (spec->dig_out) {
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+ ca0132_pcm_digital_playback;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out;
+ }
+ if (spec->dig_in) {
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+ ca0132_pcm_digital_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in;
+ }
+
+ return 0;
+}
+
+static int dbpro_build_pcms(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct hda_pcm *info;
+
+ info = snd_hda_codec_pcm_new(codec, "CA0132 Alt Analog");
+ if (!info)
+ return -ENOMEM;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0];
+
+
+ if (!spec->dig_out && !spec->dig_in)
+ return 0;
+
+ info = snd_hda_codec_pcm_new(codec, "CA0132 Digital");
+ if (!info)
+ return -ENOMEM;
+ info->pcm_type = HDA_PCM_TYPE_SPDIF;
+ if (spec->dig_out) {
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+ ca0132_pcm_digital_playback;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out;
+ }
+ if (spec->dig_in) {
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+ ca0132_pcm_digital_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in;
+ }
+
+ return 0;
+}
+
+static void init_output(struct hda_codec *codec, hda_nid_t pin, hda_nid_t dac)
+{
+ if (pin) {
+ snd_hda_set_pin_ctl(codec, pin, PIN_HP);
+ if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP)
+ snd_hda_codec_write(codec, pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE,
+ AMP_OUT_UNMUTE);
+ }
+ if (dac && (get_wcaps(codec, dac) & AC_WCAP_OUT_AMP))
+ snd_hda_codec_write(codec, dac, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO);
+}
+
+static void init_input(struct hda_codec *codec, hda_nid_t pin, hda_nid_t adc)
+{
+ if (pin) {
+ snd_hda_set_pin_ctl(codec, pin, PIN_VREF80);
+ if (get_wcaps(codec, pin) & AC_WCAP_IN_AMP)
+ snd_hda_codec_write(codec, pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE,
+ AMP_IN_UNMUTE(0));
+ }
+ if (adc && (get_wcaps(codec, adc) & AC_WCAP_IN_AMP)) {
+ snd_hda_codec_write(codec, adc, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+ AMP_IN_UNMUTE(0));
+
+ /* init to 0 dB and unmute. */
+ snd_hda_codec_amp_stereo(codec, adc, HDA_INPUT, 0,
+ HDA_AMP_VOLMASK, 0x5a);
+ snd_hda_codec_amp_stereo(codec, adc, HDA_INPUT, 0,
+ HDA_AMP_MUTE, 0);
+ }
+}
+
+static void refresh_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir)
+{
+ unsigned int caps;
+
+ caps = snd_hda_param_read(codec, nid, dir == HDA_OUTPUT ?
+ AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+ snd_hda_override_amp_caps(codec, nid, dir, caps);
+}
+
+/*
+ * Switch between Digital built-in mic and analog mic.
+ */
+static void ca0132_set_dmic(struct hda_codec *codec, int enable)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ u8 val;
+ unsigned int oldval;
+
+ codec_dbg(codec, "ca0132_set_dmic: enable=%d\n", enable);
+
+ oldval = stop_mic1(codec);
+ ca0132_set_vipsource(codec, 0);
+ if (enable) {
+ /* set DMic input as 2-ch */
+ tmp = FLOAT_TWO;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ val = spec->dmic_ctl;
+ val |= 0x80;
+ snd_hda_codec_write(codec, spec->input_pins[0], 0,
+ VENDOR_CHIPIO_DMIC_CTL_SET, val);
+
+ if (!(spec->dmic_ctl & 0x20))
+ chipio_set_control_flag(codec, CONTROL_FLAG_DMIC, 1);
+ } else {
+ /* set AMic input as mono */
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ val = spec->dmic_ctl;
+ /* clear bit7 and bit5 to disable dmic */
+ val &= 0x5f;
+ snd_hda_codec_write(codec, spec->input_pins[0], 0,
+ VENDOR_CHIPIO_DMIC_CTL_SET, val);
+
+ if (!(spec->dmic_ctl & 0x20))
+ chipio_set_control_flag(codec, CONTROL_FLAG_DMIC, 0);
+ }
+ ca0132_set_vipsource(codec, 1);
+ resume_mic1(codec, oldval);
+}
+
+/*
+ * Initialization for Digital Mic.
+ */
+static void ca0132_init_dmic(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ u8 val;
+
+ /* Setup Digital Mic here, but don't enable.
+ * Enable based on jack detect.
+ */
+
+ /* MCLK uses MPIO1, set to enable.
+ * Bit 2-0: MPIO select
+ * Bit 3: set to disable
+ * Bit 7-4: reserved
+ */
+ val = 0x01;
+ snd_hda_codec_write(codec, spec->input_pins[0], 0,
+ VENDOR_CHIPIO_DMIC_MCLK_SET, val);
+
+ /* Data1 uses MPIO3. Data2 not use
+ * Bit 2-0: Data1 MPIO select
+ * Bit 3: set disable Data1
+ * Bit 6-4: Data2 MPIO select
+ * Bit 7: set disable Data2
+ */
+ val = 0x83;
+ snd_hda_codec_write(codec, spec->input_pins[0], 0,
+ VENDOR_CHIPIO_DMIC_PIN_SET, val);
+
+ /* Use Ch-0 and Ch-1. Rate is 48K, mode 1. Disable DMic first.
+ * Bit 3-0: Channel mask
+ * Bit 4: set for 48KHz, clear for 32KHz
+ * Bit 5: mode
+ * Bit 6: set to select Data2, clear for Data1
+ * Bit 7: set to enable DMic, clear for AMic
+ */
+ if (ca0132_quirk(spec) == QUIRK_ALIENWARE_M17XR4)
+ val = 0x33;
+ else
+ val = 0x23;
+ /* keep a copy of dmic ctl val for enable/disable dmic purpuse */
+ spec->dmic_ctl = val;
+ snd_hda_codec_write(codec, spec->input_pins[0], 0,
+ VENDOR_CHIPIO_DMIC_CTL_SET, val);
+}
+
+/*
+ * Initialization for Analog Mic 2
+ */
+static void ca0132_init_analog_mic2(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x20);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x19);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x00);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x2D);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x19);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x00);
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+static void ca0132_refresh_widget_caps(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int i;
+
+ codec_dbg(codec, "ca0132_refresh_widget_caps.\n");
+ snd_hda_codec_update_widgets(codec);
+
+ for (i = 0; i < spec->multiout.num_dacs; i++)
+ refresh_amp_caps(codec, spec->dacs[i], HDA_OUTPUT);
+
+ for (i = 0; i < spec->num_outputs; i++)
+ refresh_amp_caps(codec, spec->out_pins[i], HDA_OUTPUT);
+
+ for (i = 0; i < spec->num_inputs; i++) {
+ refresh_amp_caps(codec, spec->adcs[i], HDA_INPUT);
+ refresh_amp_caps(codec, spec->input_pins[i], HDA_INPUT);
+ }
+}
+
+/*
+ * Default speaker tuning values setup for alternative codecs.
+ */
+static const unsigned int sbz_default_delay_values[] = {
+ /* Non-zero values are floating point 0.000198. */
+ 0x394f9e38, 0x394f9e38, 0x00000000, 0x00000000, 0x00000000, 0x00000000
+};
+
+static const unsigned int zxr_default_delay_values[] = {
+ /* Non-zero values are floating point 0.000220. */
+ 0x00000000, 0x00000000, 0x3966afcd, 0x3966afcd, 0x3966afcd, 0x3966afcd
+};
+
+static const unsigned int ae5_default_delay_values[] = {
+ /* Non-zero values are floating point 0.000100. */
+ 0x00000000, 0x00000000, 0x38d1b717, 0x38d1b717, 0x38d1b717, 0x38d1b717
+};
+
+/*
+ * If we never change these, probably only need them on initialization.
+ */
+static void ca0132_alt_init_speaker_tuning(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int i, tmp, start_req, end_req;
+ const unsigned int *values;
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ values = sbz_default_delay_values;
+ break;
+ case QUIRK_ZXR:
+ values = zxr_default_delay_values;
+ break;
+ case QUIRK_AE5:
+ case QUIRK_AE7:
+ values = ae5_default_delay_values;
+ break;
+ default:
+ values = sbz_default_delay_values;
+ break;
+ }
+
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, SPEAKER_TUNING_ENABLE_CENTER_EQ, tmp);
+
+ start_req = SPEAKER_TUNING_FRONT_LEFT_VOL_LEVEL;
+ end_req = SPEAKER_TUNING_REAR_RIGHT_VOL_LEVEL;
+ for (i = start_req; i < end_req + 1; i++)
+ dspio_set_uint_param(codec, 0x96, i, tmp);
+
+ start_req = SPEAKER_TUNING_FRONT_LEFT_INVERT;
+ end_req = SPEAKER_TUNING_REAR_RIGHT_INVERT;
+ for (i = start_req; i < end_req + 1; i++)
+ dspio_set_uint_param(codec, 0x96, i, tmp);
+
+
+ for (i = 0; i < 6; i++)
+ dspio_set_uint_param(codec, 0x96,
+ SPEAKER_TUNING_FRONT_LEFT_DELAY + i, values[i]);
+}
+
+/*
+ * Creates a dummy stream to bind the output to. This seems to have to be done
+ * after changing the main outputs source and destination streams.
+ */
+static void ca0132_alt_create_dummy_stream(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int stream_format;
+
+ stream_format = snd_hdac_calc_stream_format(48000, 2,
+ SNDRV_PCM_FORMAT_S32_LE, 32, 0);
+
+ snd_hda_codec_setup_stream(codec, spec->dacs[0], spec->dsp_stream_id,
+ 0, stream_format);
+
+ snd_hda_codec_cleanup_stream(codec, spec->dacs[0]);
+}
+
+/*
+ * Initialize mic for non-chromebook ca0132 implementations.
+ */
+static void ca0132_alt_init_analog_mics(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+
+ /* Mic 1 Setup */
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI) {
+ chipio_set_conn_rate(codec, 0x0F, SR_96_000);
+ tmp = FLOAT_ONE;
+ } else
+ tmp = FLOAT_THREE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ /* Mic 2 setup (not present on desktop cards) */
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000);
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ chipio_set_conn_rate(codec, 0x0F, SR_96_000);
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x80, 0x01, tmp);
+}
+
+/*
+ * Sets the source of stream 0x14 to connpointID 0x48, and the destination
+ * connpointID to 0x91. If this isn't done, the destination is 0x71, and
+ * you get no sound. I'm guessing this has to do with the Sound Blaster Z
+ * having an updated DAC, which changes the destination to that DAC.
+ */
+static void sbz_connect_streams(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ codec_dbg(codec, "Connect Streams entered, mutex locked and loaded.\n");
+
+ chipio_set_stream_channels(codec, 0x0C, 6);
+ chipio_set_stream_control(codec, 0x0C, 1);
+
+ /* This value is 0x43 for 96khz, and 0x83 for 192khz. */
+ chipio_write_no_mutex(codec, 0x18a020, 0x00000043);
+
+ /* Setup stream 0x14 with it's source and destination points */
+ chipio_set_stream_source_dest(codec, 0x14, 0x48, 0x91);
+ chipio_set_conn_rate_no_mutex(codec, 0x48, SR_96_000);
+ chipio_set_conn_rate_no_mutex(codec, 0x91, SR_96_000);
+ chipio_set_stream_channels(codec, 0x14, 2);
+ chipio_set_stream_control(codec, 0x14, 1);
+
+ codec_dbg(codec, "Connect Streams exited, mutex released.\n");
+
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+/*
+ * Write data through ChipIO to setup proper stream destinations.
+ * Not sure how it exactly works, but it seems to direct data
+ * to different destinations. Example is f8 to c0, e0 to c0.
+ * All I know is, if you don't set these, you get no sound.
+ */
+static void sbz_chipio_startup_data(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+ codec_dbg(codec, "Startup Data entered, mutex locked and loaded.\n");
+
+ /* These control audio output */
+ chipio_write_no_mutex(codec, 0x190060, 0x0001f8c0);
+ chipio_write_no_mutex(codec, 0x190064, 0x0001f9c1);
+ chipio_write_no_mutex(codec, 0x190068, 0x0001fac6);
+ chipio_write_no_mutex(codec, 0x19006c, 0x0001fbc7);
+ /* Signal to update I think */
+ chipio_write_no_mutex(codec, 0x19042c, 0x00000001);
+
+ chipio_set_stream_channels(codec, 0x0C, 6);
+ chipio_set_stream_control(codec, 0x0C, 1);
+ /* No clue what these control */
+ if (ca0132_quirk(spec) == QUIRK_SBZ) {
+ chipio_write_no_mutex(codec, 0x190030, 0x0001e0c0);
+ chipio_write_no_mutex(codec, 0x190034, 0x0001e1c1);
+ chipio_write_no_mutex(codec, 0x190038, 0x0001e4c2);
+ chipio_write_no_mutex(codec, 0x19003c, 0x0001e5c3);
+ chipio_write_no_mutex(codec, 0x190040, 0x0001e2c4);
+ chipio_write_no_mutex(codec, 0x190044, 0x0001e3c5);
+ chipio_write_no_mutex(codec, 0x190048, 0x0001e8c6);
+ chipio_write_no_mutex(codec, 0x19004c, 0x0001e9c7);
+ chipio_write_no_mutex(codec, 0x190050, 0x0001ecc8);
+ chipio_write_no_mutex(codec, 0x190054, 0x0001edc9);
+ chipio_write_no_mutex(codec, 0x190058, 0x0001eaca);
+ chipio_write_no_mutex(codec, 0x19005c, 0x0001ebcb);
+ } else if (ca0132_quirk(spec) == QUIRK_ZXR) {
+ chipio_write_no_mutex(codec, 0x190038, 0x000140c2);
+ chipio_write_no_mutex(codec, 0x19003c, 0x000141c3);
+ chipio_write_no_mutex(codec, 0x190040, 0x000150c4);
+ chipio_write_no_mutex(codec, 0x190044, 0x000151c5);
+ chipio_write_no_mutex(codec, 0x190050, 0x000142c8);
+ chipio_write_no_mutex(codec, 0x190054, 0x000143c9);
+ chipio_write_no_mutex(codec, 0x190058, 0x000152ca);
+ chipio_write_no_mutex(codec, 0x19005c, 0x000153cb);
+ }
+ chipio_write_no_mutex(codec, 0x19042c, 0x00000001);
+
+ codec_dbg(codec, "Startup Data exited, mutex released.\n");
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+/*
+ * Custom DSP SCP commands where the src value is 0x00 instead of 0x20. This is
+ * done after the DSP is loaded.
+ */
+static void ca0132_alt_dsp_scp_startup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp, i;
+
+ /*
+ * Gotta run these twice, or else mic works inconsistently. Not clear
+ * why this is, but multiple tests have confirmed it.
+ */
+ for (i = 0; i < 2; i++) {
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_AE5:
+ case QUIRK_AE7:
+ tmp = 0x00000003;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ tmp = 0x00000000;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0A, tmp);
+ tmp = 0x00000001;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0B, tmp);
+ tmp = 0x00000004;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ tmp = 0x00000005;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ tmp = 0x00000000;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ break;
+ case QUIRK_R3D:
+ case QUIRK_R3DI:
+ tmp = 0x00000000;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0A, tmp);
+ tmp = 0x00000001;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0B, tmp);
+ tmp = 0x00000004;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ tmp = 0x00000005;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ tmp = 0x00000000;
+ dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp);
+ break;
+ default:
+ break;
+ }
+ msleep(100);
+ }
+}
+
+static void ca0132_alt_dsp_initial_mic_setup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+
+ chipio_set_stream_control(codec, 0x03, 0);
+ chipio_set_stream_control(codec, 0x04, 0);
+
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+
+ tmp = FLOAT_THREE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ chipio_write(codec, 0x18b098, 0x0000000c);
+ chipio_write(codec, 0x18b09C, 0x0000000c);
+ break;
+ case QUIRK_AE5:
+ chipio_write(codec, 0x18b098, 0x0000000c);
+ chipio_write(codec, 0x18b09c, 0x0000004c);
+ break;
+ default:
+ break;
+ }
+}
+
+static void ae5_post_dsp_register_set(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ chipio_8051_write_direct(codec, 0x93, 0x10);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x44);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc2);
+
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0x00, spec->mem_base + 0x100);
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0x00, spec->mem_base + 0x100);
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0x00, spec->mem_base + 0x100);
+ writeb(0xff, spec->mem_base + 0x304);
+ writeb(0x00, spec->mem_base + 0x100);
+ writeb(0xff, spec->mem_base + 0x304);
+
+ ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x3f);
+ ca0113_mmio_command_set(codec, 0x30, 0x2d, 0x3f);
+ ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83);
+}
+
+static void ae5_post_dsp_param_setup(struct hda_codec *codec)
+{
+ /*
+ * Param3 in the 8051's memory is represented by the ascii string 'mch'
+ * which seems to be 'multichannel'. This is also mentioned in the
+ * AE-5's registry values in Windows.
+ */
+ chipio_set_control_param(codec, 3, 0);
+ /*
+ * I believe ASI is 'audio serial interface' and that it's used to
+ * change colors on the external LED strip connected to the AE-5.
+ */
+ chipio_set_control_flag(codec, CONTROL_FLAG_ASI_96KHZ, 1);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x724, 0x83);
+ chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x92);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0xfa);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x22);
+}
+
+static void ae5_post_dsp_pll_setup(struct hda_codec *codec)
+{
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x41);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc8);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x45);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xcc);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x40);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xcb);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x43);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc7);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x51);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0x8d);
+}
+
+static void ae5_post_dsp_stream_setup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x725, 0x81);
+
+ chipio_set_conn_rate_no_mutex(codec, 0x70, SR_96_000);
+
+ chipio_set_stream_channels(codec, 0x0C, 6);
+ chipio_set_stream_control(codec, 0x0C, 1);
+
+ chipio_set_stream_source_dest(codec, 0x5, 0x43, 0x0);
+
+ chipio_set_stream_source_dest(codec, 0x18, 0x9, 0xd0);
+ chipio_set_conn_rate_no_mutex(codec, 0xd0, SR_96_000);
+ chipio_set_stream_channels(codec, 0x18, 6);
+ chipio_set_stream_control(codec, 0x18, 1);
+
+ chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 4);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x43);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc7);
+
+ ca0113_mmio_command_set(codec, 0x48, 0x01, 0x80);
+
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+static void ae5_post_dsp_startup_data(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ chipio_write_no_mutex(codec, 0x189000, 0x0001f101);
+ chipio_write_no_mutex(codec, 0x189004, 0x0001f101);
+ chipio_write_no_mutex(codec, 0x189024, 0x00014004);
+ chipio_write_no_mutex(codec, 0x189028, 0x0002000f);
+
+ ca0113_mmio_command_set(codec, 0x48, 0x0a, 0x05);
+ chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 7);
+ ca0113_mmio_command_set(codec, 0x48, 0x0b, 0x12);
+ ca0113_mmio_command_set(codec, 0x48, 0x04, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x06, 0x48);
+ ca0113_mmio_command_set(codec, 0x48, 0x0a, 0x05);
+ ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00);
+ ca0113_mmio_gpio_set(codec, 0, true);
+ ca0113_mmio_gpio_set(codec, 1, true);
+ ca0113_mmio_command_set(codec, 0x48, 0x07, 0x80);
+
+ chipio_write_no_mutex(codec, 0x18b03c, 0x00000012);
+
+ ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00);
+
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+static const unsigned int ae7_port_set_data[] = {
+ 0x0001e0c0, 0x0001e1c1, 0x0001e4c2, 0x0001e5c3, 0x0001e2c4, 0x0001e3c5,
+ 0x0001e8c6, 0x0001e9c7, 0x0001ecc8, 0x0001edc9, 0x0001eaca, 0x0001ebcb
+};
+
+static void ae7_post_dsp_setup_ports(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int i, count, addr;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ chipio_set_stream_channels(codec, 0x0c, 6);
+ chipio_set_stream_control(codec, 0x0c, 1);
+
+ count = ARRAY_SIZE(ae7_port_set_data);
+ addr = 0x190030;
+ for (i = 0; i < count; i++) {
+ chipio_write_no_mutex(codec, addr, ae7_port_set_data[i]);
+
+ /* Addresses are incremented by 4-bytes. */
+ addr += 0x04;
+ }
+
+ /*
+ * Port setting always ends with a write of 0x1 to address 0x19042c.
+ */
+ chipio_write_no_mutex(codec, 0x19042c, 0x00000001);
+
+ ca0113_mmio_command_set(codec, 0x30, 0x30, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x0d, 0x40);
+ ca0113_mmio_command_set(codec, 0x48, 0x17, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x19, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x11, 0xff);
+ ca0113_mmio_command_set(codec, 0x48, 0x12, 0xff);
+ ca0113_mmio_command_set(codec, 0x48, 0x13, 0xff);
+ ca0113_mmio_command_set(codec, 0x48, 0x14, 0x7f);
+
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+static void ae7_post_dsp_asi_stream_setup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x725, 0x81);
+ ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x00);
+
+ chipio_set_conn_rate_no_mutex(codec, 0x70, SR_96_000);
+ chipio_set_stream_channels(codec, 0x0c, 6);
+ chipio_set_stream_control(codec, 0x0c, 1);
+
+ chipio_set_stream_source_dest(codec, 0x05, 0x43, 0x00);
+ chipio_set_stream_source_dest(codec, 0x18, 0x09, 0xd0);
+
+ chipio_set_conn_rate_no_mutex(codec, 0xd0, SR_96_000);
+ chipio_set_stream_channels(codec, 0x18, 6);
+ chipio_set_stream_control(codec, 0x18, 1);
+
+ chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 4);
+
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+static void ae7_post_dsp_pll_setup(struct hda_codec *codec)
+{
+ static const unsigned int addr[] = {
+ 0x41, 0x45, 0x40, 0x43, 0x51
+ };
+ static const unsigned int data[] = {
+ 0xc8, 0xcc, 0xcb, 0xc7, 0x8d
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(addr); i++) {
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, addr[i]);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, data[i]);
+ }
+}
+
+static void ae7_post_dsp_asi_setup_ports(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ static const unsigned int target[] = {
+ 0x0b, 0x04, 0x06, 0x0a, 0x0c, 0x11, 0x12, 0x13, 0x14
+ };
+ static const unsigned int data[] = {
+ 0x12, 0x00, 0x48, 0x05, 0x5f, 0xff, 0xff, 0xff, 0x7f
+ };
+ unsigned int i;
+
+ mutex_lock(&spec->chipio_mutex);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x43);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc7);
+
+ chipio_write_no_mutex(codec, 0x189000, 0x0001f101);
+ chipio_write_no_mutex(codec, 0x189004, 0x0001f101);
+ chipio_write_no_mutex(codec, 0x189024, 0x00014004);
+ chipio_write_no_mutex(codec, 0x189028, 0x0002000f);
+
+ ae7_post_dsp_pll_setup(codec);
+ chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 7);
+
+ for (i = 0; i < ARRAY_SIZE(target); i++)
+ ca0113_mmio_command_set(codec, 0x48, target[i], data[i]);
+
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00);
+
+ chipio_set_stream_source_dest(codec, 0x21, 0x64, 0x56);
+ chipio_set_stream_channels(codec, 0x21, 2);
+ chipio_set_conn_rate_no_mutex(codec, 0x56, SR_8_000);
+
+ chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_NODE_ID, 0x09);
+ /*
+ * In the 8051's memory, this param is referred to as 'n2sid', which I
+ * believe is 'node to streamID'. It seems to be a way to assign a
+ * stream to a given HDA node.
+ */
+ chipio_set_control_param_no_mutex(codec, 0x20, 0x21);
+
+ chipio_write_no_mutex(codec, 0x18b038, 0x00000088);
+
+ /*
+ * Now, at this point on Windows, an actual stream is setup and
+ * seemingly sends data to the HDA node 0x09, which is the digital
+ * audio input node. This is left out here, because obviously I don't
+ * know what data is being sent. Interestingly, the AE-5 seems to go
+ * through the motions of getting here and never actually takes this
+ * step, but the AE-7 does.
+ */
+
+ ca0113_mmio_gpio_set(codec, 0, 1);
+ ca0113_mmio_gpio_set(codec, 1, 1);
+
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83);
+ chipio_write_no_mutex(codec, 0x18b03c, 0x00000000);
+ ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00);
+ ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00);
+
+ chipio_set_stream_source_dest(codec, 0x05, 0x43, 0x00);
+ chipio_set_stream_source_dest(codec, 0x18, 0x09, 0xd0);
+
+ chipio_set_conn_rate_no_mutex(codec, 0xd0, SR_96_000);
+ chipio_set_stream_channels(codec, 0x18, 6);
+
+ /*
+ * Runs again, this has been repeated a few times, but I'm just
+ * following what the Windows driver does.
+ */
+ ae7_post_dsp_pll_setup(codec);
+ chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 7);
+
+ mutex_unlock(&spec->chipio_mutex);
+}
+
+/*
+ * The Windows driver has commands that seem to setup ASI, which I believe to
+ * be some sort of audio serial interface. My current speculation is that it's
+ * related to communicating with the new DAC.
+ */
+static void ae7_post_dsp_asi_setup(struct hda_codec *codec)
+{
+ chipio_8051_write_direct(codec, 0x93, 0x10);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x44);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc2);
+
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f);
+
+ chipio_set_control_param(codec, 3, 3);
+ chipio_set_control_flag(codec, CONTROL_FLAG_ASI_96KHZ, 1);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x724, 0x83);
+ chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0);
+ snd_hda_codec_write(codec, 0x17, 0, 0x794, 0x00);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x92);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0xfa);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x22);
+
+ ae7_post_dsp_pll_setup(codec);
+ ae7_post_dsp_asi_stream_setup(codec);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x43);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc7);
+
+ ae7_post_dsp_asi_setup_ports(codec);
+}
+
+/*
+ * Setup default parameters for DSP
+ */
+static void ca0132_setup_defaults(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ int num_fx;
+ int idx, i;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return;
+
+ /* out, in effects + voicefx */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1;
+ for (idx = 0; idx < num_fx; idx++) {
+ for (i = 0; i <= ca0132_effects[idx].params; i++) {
+ dspio_set_uint_param(codec, ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[i],
+ ca0132_effects[idx].def_vals[i]);
+ }
+ }
+
+ /*remove DSP headroom*/
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, 0x3C, tmp);
+
+ /*set speaker EQ bypass attenuation*/
+ dspio_set_uint_param(codec, 0x8f, 0x01, tmp);
+
+ /* set AMic1 and AMic2 as mono mic */
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x00, tmp);
+ dspio_set_uint_param(codec, 0x80, 0x01, tmp);
+
+ /* set AMic1 as CrystalVoice input */
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x80, 0x05, tmp);
+
+ /* set WUH source */
+ tmp = FLOAT_TWO;
+ dspio_set_uint_param(codec, 0x31, 0x00, tmp);
+}
+
+/*
+ * Setup default parameters for Recon3D/Recon3Di DSP.
+ */
+
+static void r3d_setup_defaults(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ int num_fx;
+ int idx, i;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return;
+
+ ca0132_alt_dsp_scp_startup(codec);
+ ca0132_alt_init_analog_mics(codec);
+
+ /*remove DSP headroom*/
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, 0x3C, tmp);
+
+ /* set WUH source */
+ tmp = FLOAT_TWO;
+ dspio_set_uint_param(codec, 0x31, 0x00, tmp);
+ chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000);
+
+ /* Set speaker source? */
+ dspio_set_uint_param(codec, 0x32, 0x00, tmp);
+
+ if (ca0132_quirk(spec) == QUIRK_R3DI)
+ r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADED);
+
+ /* Disable mute on Center/LFE. */
+ if (ca0132_quirk(spec) == QUIRK_R3D) {
+ ca0113_mmio_gpio_set(codec, 2, false);
+ ca0113_mmio_gpio_set(codec, 4, true);
+ }
+
+ /* Setup effect defaults */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1;
+ for (idx = 0; idx < num_fx; idx++) {
+ for (i = 0; i <= ca0132_effects[idx].params; i++) {
+ dspio_set_uint_param(codec,
+ ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[i],
+ ca0132_effects[idx].def_vals[i]);
+ }
+ }
+}
+
+/*
+ * Setup default parameters for the Sound Blaster Z DSP. A lot more going on
+ * than the Chromebook setup.
+ */
+static void sbz_setup_defaults(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ int num_fx;
+ int idx, i;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return;
+
+ ca0132_alt_dsp_scp_startup(codec);
+ ca0132_alt_init_analog_mics(codec);
+ sbz_connect_streams(codec);
+ sbz_chipio_startup_data(codec);
+
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+
+ /*
+ * Sets internal input loopback to off, used to have a switch to
+ * enable input loopback, but turned out to be way too buggy.
+ */
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x37, 0x08, tmp);
+ dspio_set_uint_param(codec, 0x37, 0x10, tmp);
+
+ /*remove DSP headroom*/
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, 0x3C, tmp);
+
+ /* set WUH source */
+ tmp = FLOAT_TWO;
+ dspio_set_uint_param(codec, 0x31, 0x00, tmp);
+ chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000);
+
+ /* Set speaker source? */
+ dspio_set_uint_param(codec, 0x32, 0x00, tmp);
+
+ ca0132_alt_dsp_initial_mic_setup(codec);
+
+ /* out, in effects + voicefx */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1;
+ for (idx = 0; idx < num_fx; idx++) {
+ for (i = 0; i <= ca0132_effects[idx].params; i++) {
+ dspio_set_uint_param(codec,
+ ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[i],
+ ca0132_effects[idx].def_vals[i]);
+ }
+ }
+
+ ca0132_alt_init_speaker_tuning(codec);
+
+ ca0132_alt_create_dummy_stream(codec);
+}
+
+/*
+ * Setup default parameters for the Sound BlasterX AE-5 DSP.
+ */
+static void ae5_setup_defaults(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ int num_fx;
+ int idx, i;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return;
+
+ ca0132_alt_dsp_scp_startup(codec);
+ ca0132_alt_init_analog_mics(codec);
+ chipio_set_stream_control(codec, 0x03, 1);
+ chipio_set_stream_control(codec, 0x04, 1);
+
+ /* New, unknown SCP req's */
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, 0x29, tmp);
+ dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
+ dspio_set_uint_param(codec, 0x80, 0x0d, tmp);
+ dspio_set_uint_param(codec, 0x80, 0x0e, tmp);
+
+ ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f);
+ ca0113_mmio_gpio_set(codec, 0, false);
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00);
+
+ /* Internal loopback off */
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x37, 0x08, tmp);
+ dspio_set_uint_param(codec, 0x37, 0x10, tmp);
+
+ /*remove DSP headroom*/
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, 0x3C, tmp);
+
+ /* set WUH source */
+ tmp = FLOAT_TWO;
+ dspio_set_uint_param(codec, 0x31, 0x00, tmp);
+ chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000);
+
+ /* Set speaker source? */
+ dspio_set_uint_param(codec, 0x32, 0x00, tmp);
+
+ ca0132_alt_dsp_initial_mic_setup(codec);
+ ae5_post_dsp_register_set(codec);
+ ae5_post_dsp_param_setup(codec);
+ ae5_post_dsp_pll_setup(codec);
+ ae5_post_dsp_stream_setup(codec);
+ ae5_post_dsp_startup_data(codec);
+
+ /* out, in effects + voicefx */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1;
+ for (idx = 0; idx < num_fx; idx++) {
+ for (i = 0; i <= ca0132_effects[idx].params; i++) {
+ dspio_set_uint_param(codec,
+ ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[i],
+ ca0132_effects[idx].def_vals[i]);
+ }
+ }
+
+ ca0132_alt_init_speaker_tuning(codec);
+
+ ca0132_alt_create_dummy_stream(codec);
+}
+
+/*
+ * Setup default parameters for the Sound Blaster AE-7 DSP.
+ */
+static void ae7_setup_defaults(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp;
+ int num_fx;
+ int idx, i;
+
+ if (spec->dsp_state != DSP_DOWNLOADED)
+ return;
+
+ ca0132_alt_dsp_scp_startup(codec);
+ ca0132_alt_init_analog_mics(codec);
+ ae7_post_dsp_setup_ports(codec);
+
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96,
+ SPEAKER_TUNING_FRONT_LEFT_INVERT, tmp);
+ dspio_set_uint_param(codec, 0x96,
+ SPEAKER_TUNING_FRONT_RIGHT_INVERT, tmp);
+
+ ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f);
+
+ /* New, unknown SCP req's */
+ dspio_set_uint_param(codec, 0x80, 0x0d, tmp);
+ dspio_set_uint_param(codec, 0x80, 0x0e, tmp);
+
+ ca0113_mmio_gpio_set(codec, 0, false);
+
+ /* Internal loopback off */
+ tmp = FLOAT_ONE;
+ dspio_set_uint_param(codec, 0x37, 0x08, tmp);
+ dspio_set_uint_param(codec, 0x37, 0x10, tmp);
+
+ /*remove DSP headroom*/
+ tmp = FLOAT_ZERO;
+ dspio_set_uint_param(codec, 0x96, 0x3C, tmp);
+
+ /* set WUH source */
+ tmp = FLOAT_TWO;
+ dspio_set_uint_param(codec, 0x31, 0x00, tmp);
+ chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000);
+
+ /* Set speaker source? */
+ dspio_set_uint_param(codec, 0x32, 0x00, tmp);
+ ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00);
+
+ /*
+ * This is the second time we've called this, but this is seemingly
+ * what Windows does.
+ */
+ ca0132_alt_init_analog_mics(codec);
+
+ ae7_post_dsp_asi_setup(codec);
+
+ /*
+ * Not sure why, but these are both set to 1. They're only set to 0
+ * upon shutdown.
+ */
+ ca0113_mmio_gpio_set(codec, 0, true);
+ ca0113_mmio_gpio_set(codec, 1, true);
+
+ /* Volume control related. */
+ ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x04);
+ ca0113_mmio_command_set(codec, 0x48, 0x10, 0x04);
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x80);
+
+ /* out, in effects + voicefx */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1;
+ for (idx = 0; idx < num_fx; idx++) {
+ for (i = 0; i <= ca0132_effects[idx].params; i++) {
+ dspio_set_uint_param(codec,
+ ca0132_effects[idx].mid,
+ ca0132_effects[idx].reqs[i],
+ ca0132_effects[idx].def_vals[i]);
+ }
+ }
+
+ ca0132_alt_init_speaker_tuning(codec);
+
+ ca0132_alt_create_dummy_stream(codec);
+}
+
+/*
+ * Initialization of flags in chip
+ */
+static void ca0132_init_flags(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ if (ca0132_use_alt_functions(spec)) {
+ chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, 1);
+ chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, 1);
+ chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, 1);
+ chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, 1);
+ chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, 1);
+ chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0);
+ chipio_set_control_flag(codec, CONTROL_FLAG_SPDIF2OUT, 0);
+ chipio_set_control_flag(codec,
+ CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0);
+ chipio_set_control_flag(codec,
+ CONTROL_FLAG_PORT_A_10KOHM_LOAD, 1);
+ } else {
+ chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0);
+ chipio_set_control_flag(codec,
+ CONTROL_FLAG_PORT_A_COMMON_MODE, 0);
+ chipio_set_control_flag(codec,
+ CONTROL_FLAG_PORT_D_COMMON_MODE, 0);
+ chipio_set_control_flag(codec,
+ CONTROL_FLAG_PORT_A_10KOHM_LOAD, 0);
+ chipio_set_control_flag(codec,
+ CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0);
+ chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_HIGH_PASS, 1);
+ }
+}
+
+/*
+ * Initialization of parameters in chip
+ */
+static void ca0132_init_params(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ if (ca0132_use_alt_functions(spec)) {
+ chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000);
+ chipio_set_conn_rate(codec, 0x0B, SR_48_000);
+ chipio_set_control_param(codec, CONTROL_PARAM_SPDIF1_SOURCE, 0);
+ chipio_set_control_param(codec, 0, 0);
+ chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0);
+ }
+
+ chipio_set_control_param(codec, CONTROL_PARAM_PORTA_160OHM_GAIN, 6);
+ chipio_set_control_param(codec, CONTROL_PARAM_PORTD_160OHM_GAIN, 6);
+}
+
+static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k)
+{
+ chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, is96k);
+ chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, is96k);
+ chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, is96k);
+ chipio_set_control_flag(codec, CONTROL_FLAG_SRC_CLOCK_196MHZ, is96k);
+ chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, is96k);
+ chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, is96k);
+
+ chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000);
+ chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000);
+}
+
+static bool ca0132_download_dsp_images(struct hda_codec *codec)
+{
+ bool dsp_loaded = false;
+ struct ca0132_spec *spec = codec->spec;
+ const struct dsp_image_seg *dsp_os_image;
+ const struct firmware *fw_entry = NULL;
+ /*
+ * Alternate firmwares for different variants. The Recon3Di apparently
+ * can use the default firmware, but I'll leave the option in case
+ * it needs it again.
+ */
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_R3D:
+ case QUIRK_AE5:
+ if (request_firmware(&fw_entry, DESKTOP_EFX_FILE,
+ codec->card->dev) != 0)
+ codec_dbg(codec, "Desktop firmware not found.");
+ else
+ codec_dbg(codec, "Desktop firmware selected.");
+ break;
+ case QUIRK_R3DI:
+ if (request_firmware(&fw_entry, R3DI_EFX_FILE,
+ codec->card->dev) != 0)
+ codec_dbg(codec, "Recon3Di alt firmware not detected.");
+ else
+ codec_dbg(codec, "Recon3Di firmware selected.");
+ break;
+ default:
+ break;
+ }
+ /*
+ * Use default ctefx.bin if no alt firmware is detected, or if none
+ * exists for your particular codec.
+ */
+ if (!fw_entry) {
+ codec_dbg(codec, "Default firmware selected.");
+ if (request_firmware(&fw_entry, EFX_FILE,
+ codec->card->dev) != 0)
+ return false;
+ }
+
+ dsp_os_image = (struct dsp_image_seg *)(fw_entry->data);
+ if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) {
+ codec_err(codec, "ca0132 DSP load image failed\n");
+ goto exit_download;
+ }
+
+ dsp_loaded = dspload_wait_loaded(codec);
+
+exit_download:
+ release_firmware(fw_entry);
+
+ return dsp_loaded;
+}
+
+static void ca0132_download_dsp(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+#ifndef CONFIG_SND_HDA_CODEC_CA0132_DSP
+ return; /* NOP */
+#endif
+
+ if (spec->dsp_state == DSP_DOWNLOAD_FAILED)
+ return; /* don't retry failures */
+
+ chipio_enable_clocks(codec);
+ if (spec->dsp_state != DSP_DOWNLOADED) {
+ spec->dsp_state = DSP_DOWNLOADING;
+
+ if (!ca0132_download_dsp_images(codec))
+ spec->dsp_state = DSP_DOWNLOAD_FAILED;
+ else
+ spec->dsp_state = DSP_DOWNLOADED;
+ }
+
+ /* For codecs using alt functions, this is already done earlier */
+ if (spec->dsp_state == DSP_DOWNLOADED && !ca0132_use_alt_functions(spec))
+ ca0132_set_dsp_msr(codec, true);
+}
+
+static void ca0132_process_dsp_response(struct hda_codec *codec,
+ struct hda_jack_callback *callback)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ codec_dbg(codec, "ca0132_process_dsp_response\n");
+ snd_hda_power_up_pm(codec);
+ if (spec->wait_scp) {
+ if (dspio_get_response_data(codec) >= 0)
+ spec->wait_scp = 0;
+ }
+
+ dspio_clear_response_queue(codec);
+ snd_hda_power_down_pm(codec);
+}
+
+static void hp_callback(struct hda_codec *codec, struct hda_jack_callback *cb)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct hda_jack_tbl *tbl;
+
+ /* Delay enabling the HP amp, to let the mic-detection
+ * state machine run.
+ */
+ tbl = snd_hda_jack_tbl_get(codec, cb->nid);
+ if (tbl)
+ tbl->block_report = 1;
+ schedule_delayed_work(&spec->unsol_hp_work, msecs_to_jiffies(500));
+}
+
+static void amic_callback(struct hda_codec *codec, struct hda_jack_callback *cb)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_select_in(codec);
+ else
+ ca0132_select_mic(codec);
+}
+
+static void ca0132_init_unsol(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ snd_hda_jack_detect_enable_callback(codec, spec->unsol_tag_hp, hp_callback);
+ snd_hda_jack_detect_enable_callback(codec, spec->unsol_tag_amic1,
+ amic_callback);
+ snd_hda_jack_detect_enable_callback(codec, UNSOL_TAG_DSP,
+ ca0132_process_dsp_response);
+ /* Front headphone jack detection */
+ if (ca0132_use_alt_functions(spec))
+ snd_hda_jack_detect_enable_callback(codec,
+ spec->unsol_tag_front_hp, hp_callback);
+}
+
+/*
+ * Verbs tables.
+ */
+
+/* Sends before DSP download. */
+static const struct hda_verb ca0132_base_init_verbs[] = {
+ /*enable ct extension*/
+ {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0x1},
+ {}
+};
+
+/* Send at exit. */
+static const struct hda_verb ca0132_base_exit_verbs[] = {
+ /*set afg to D3*/
+ {0x01, AC_VERB_SET_POWER_STATE, 0x03},
+ /*disable ct extension*/
+ {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0},
+ {}
+};
+
+/* Other verbs tables. Sends after DSP download. */
+
+static const struct hda_verb ca0132_init_verbs0[] = {
+ /* chip init verbs */
+ {0x15, 0x70D, 0xF0},
+ {0x15, 0x70E, 0xFE},
+ {0x15, 0x707, 0x75},
+ {0x15, 0x707, 0xD3},
+ {0x15, 0x707, 0x09},
+ {0x15, 0x707, 0x53},
+ {0x15, 0x707, 0xD4},
+ {0x15, 0x707, 0xEF},
+ {0x15, 0x707, 0x75},
+ {0x15, 0x707, 0xD3},
+ {0x15, 0x707, 0x09},
+ {0x15, 0x707, 0x02},
+ {0x15, 0x707, 0x37},
+ {0x15, 0x707, 0x78},
+ {0x15, 0x53C, 0xCE},
+ {0x15, 0x575, 0xC9},
+ {0x15, 0x53D, 0xCE},
+ {0x15, 0x5B7, 0xC9},
+ {0x15, 0x70D, 0xE8},
+ {0x15, 0x70E, 0xFE},
+ {0x15, 0x707, 0x02},
+ {0x15, 0x707, 0x68},
+ {0x15, 0x707, 0x62},
+ {0x15, 0x53A, 0xCE},
+ {0x15, 0x546, 0xC9},
+ {0x15, 0x53B, 0xCE},
+ {0x15, 0x5E8, 0xC9},
+ {}
+};
+
+/* Extra init verbs for desktop cards. */
+static const struct hda_verb ca0132_init_verbs1[] = {
+ {0x15, 0x70D, 0x20},
+ {0x15, 0x70E, 0x19},
+ {0x15, 0x707, 0x00},
+ {0x15, 0x539, 0xCE},
+ {0x15, 0x546, 0xC9},
+ {0x15, 0x70D, 0xB7},
+ {0x15, 0x70E, 0x09},
+ {0x15, 0x707, 0x10},
+ {0x15, 0x70D, 0xAF},
+ {0x15, 0x70E, 0x09},
+ {0x15, 0x707, 0x01},
+ {0x15, 0x707, 0x05},
+ {0x15, 0x70D, 0x73},
+ {0x15, 0x70E, 0x09},
+ {0x15, 0x707, 0x14},
+ {0x15, 0x6FF, 0xC4},
+ {}
+};
+
+static void ca0132_init_chip(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ int num_fx;
+ int i;
+ unsigned int on;
+
+ mutex_init(&spec->chipio_mutex);
+
+ spec->cur_out_type = SPEAKER_OUT;
+ if (!ca0132_use_alt_functions(spec))
+ spec->cur_mic_type = DIGITAL_MIC;
+ else
+ spec->cur_mic_type = REAR_MIC;
+
+ spec->cur_mic_boost = 0;
+
+ for (i = 0; i < VNODES_COUNT; i++) {
+ spec->vnode_lvol[i] = 0x5a;
+ spec->vnode_rvol[i] = 0x5a;
+ spec->vnode_lswitch[i] = 0;
+ spec->vnode_rswitch[i] = 0;
+ }
+
+ /*
+ * Default states for effects are in ca0132_effects[].
+ */
+ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT;
+ for (i = 0; i < num_fx; i++) {
+ on = (unsigned int)ca0132_effects[i].reqs[0];
+ spec->effects_switch[i] = on ? 1 : 0;
+ }
+ /*
+ * Sets defaults for the effect slider controls, only for alternative
+ * ca0132 codecs. Also sets x-bass crossover frequency to 80hz.
+ */
+ if (ca0132_use_alt_controls(spec)) {
+ /* Set speakers to default to full range. */
+ spec->speaker_range_val[0] = 1;
+ spec->speaker_range_val[1] = 1;
+
+ spec->xbass_xover_freq = 8;
+ for (i = 0; i < EFFECT_LEVEL_SLIDERS; i++)
+ spec->fx_ctl_val[i] = effect_slider_defaults[i];
+
+ spec->bass_redirect_xover_freq = 8;
+ }
+
+ spec->voicefx_val = 0;
+ spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID] = 1;
+ spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] = 0;
+
+ /*
+ * The ZxR doesn't have a front panel header, and it's line-in is on
+ * the daughter board. So, there is no input enum control, and we need
+ * to make sure that spec->in_enum_val is set properly.
+ */
+ if (ca0132_quirk(spec) == QUIRK_ZXR)
+ spec->in_enum_val = REAR_MIC;
+
+#ifdef ENABLE_TUNING_CONTROLS
+ ca0132_init_tuning_defaults(codec);
+#endif
+}
+
+/*
+ * Recon3Di exit specific commands.
+ */
+/* prevents popping noise on shutdown */
+static void r3di_gpio_shutdown(struct hda_codec *codec)
+{
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0x00);
+}
+
+/*
+ * Sound Blaster Z exit specific commands.
+ */
+static void sbz_region2_exit(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int i;
+
+ for (i = 0; i < 4; i++)
+ writeb(0x0, spec->mem_base + 0x100);
+ for (i = 0; i < 8; i++)
+ writeb(0xb3, spec->mem_base + 0x304);
+
+ ca0113_mmio_gpio_set(codec, 0, false);
+ ca0113_mmio_gpio_set(codec, 1, false);
+ ca0113_mmio_gpio_set(codec, 4, true);
+ ca0113_mmio_gpio_set(codec, 5, false);
+ ca0113_mmio_gpio_set(codec, 7, false);
+}
+
+static void sbz_set_pin_ctl_default(struct hda_codec *codec)
+{
+ static const hda_nid_t pins[] = {0x0B, 0x0C, 0x0E, 0x12, 0x13};
+ unsigned int i;
+
+ snd_hda_codec_write(codec, 0x11, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40);
+
+ for (i = 0; i < ARRAY_SIZE(pins); i++)
+ snd_hda_codec_write(codec, pins[i], 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00);
+}
+
+static void ca0132_clear_unsolicited(struct hda_codec *codec)
+{
+ static const hda_nid_t pins[] = {0x0B, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13};
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pins); i++) {
+ snd_hda_codec_write(codec, pins[i], 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE, 0x00);
+ }
+}
+
+/* On shutdown, sends commands in sets of three */
+static void sbz_gpio_shutdown_commands(struct hda_codec *codec, int dir,
+ int mask, int data)
+{
+ if (dir >= 0)
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DIRECTION, dir);
+ if (mask >= 0)
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_MASK, mask);
+
+ if (data >= 0)
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, data);
+}
+
+static void zxr_dbpro_power_state_shutdown(struct hda_codec *codec)
+{
+ static const hda_nid_t pins[] = {0x05, 0x0c, 0x09, 0x0e, 0x08, 0x11, 0x01};
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pins); i++)
+ snd_hda_codec_write(codec, pins[i], 0,
+ AC_VERB_SET_POWER_STATE, 0x03);
+}
+
+static void sbz_exit_chip(struct hda_codec *codec)
+{
+ chipio_set_stream_control(codec, 0x03, 0);
+ chipio_set_stream_control(codec, 0x04, 0);
+
+ /* Mess with GPIO */
+ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, -1);
+ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x05);
+ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x01);
+
+ chipio_set_stream_control(codec, 0x14, 0);
+ chipio_set_stream_control(codec, 0x0C, 0);
+
+ chipio_set_conn_rate(codec, 0x41, SR_192_000);
+ chipio_set_conn_rate(codec, 0x91, SR_192_000);
+
+ chipio_write(codec, 0x18a020, 0x00000083);
+
+ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x03);
+ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x07);
+ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x06);
+
+ chipio_set_stream_control(codec, 0x0C, 0);
+
+ chipio_set_control_param(codec, 0x0D, 0x24);
+
+ ca0132_clear_unsolicited(codec);
+ sbz_set_pin_ctl_default(codec);
+
+ snd_hda_codec_write(codec, 0x0B, 0,
+ AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+
+ sbz_region2_exit(codec);
+}
+
+static void r3d_exit_chip(struct hda_codec *codec)
+{
+ ca0132_clear_unsolicited(codec);
+ snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x5b);
+}
+
+static void ae5_exit_chip(struct hda_codec *codec)
+{
+ chipio_set_stream_control(codec, 0x03, 0);
+ chipio_set_stream_control(codec, 0x04, 0);
+
+ ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f);
+ ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x30, 0x30, 0x00);
+ ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x00);
+ ca0113_mmio_command_set(codec, 0x30, 0x2d, 0x00);
+ ca0113_mmio_gpio_set(codec, 0, false);
+ ca0113_mmio_gpio_set(codec, 1, false);
+
+ snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53);
+
+ chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0);
+
+ chipio_set_stream_control(codec, 0x18, 0);
+ chipio_set_stream_control(codec, 0x0c, 0);
+
+ snd_hda_codec_write(codec, 0x01, 0, 0x724, 0x83);
+}
+
+static void ae7_exit_chip(struct hda_codec *codec)
+{
+ chipio_set_stream_control(codec, 0x18, 0);
+ chipio_set_stream_source_dest(codec, 0x21, 0xc8, 0xc8);
+ chipio_set_stream_channels(codec, 0x21, 0);
+ chipio_set_control_param(codec, CONTROL_PARAM_NODE_ID, 0x09);
+ chipio_set_control_param(codec, 0x20, 0x01);
+
+ chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0);
+
+ chipio_set_stream_control(codec, 0x18, 0);
+ chipio_set_stream_control(codec, 0x0c, 0);
+
+ ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x00);
+ snd_hda_codec_write(codec, 0x15, 0, 0x724, 0x83);
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x30, 0x30, 0x00);
+ ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x00);
+ ca0113_mmio_gpio_set(codec, 0, false);
+ ca0113_mmio_gpio_set(codec, 1, false);
+ ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f);
+
+ snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53);
+}
+
+static void zxr_exit_chip(struct hda_codec *codec)
+{
+ chipio_set_stream_control(codec, 0x03, 0);
+ chipio_set_stream_control(codec, 0x04, 0);
+ chipio_set_stream_control(codec, 0x14, 0);
+ chipio_set_stream_control(codec, 0x0C, 0);
+
+ chipio_set_conn_rate(codec, 0x41, SR_192_000);
+ chipio_set_conn_rate(codec, 0x91, SR_192_000);
+
+ chipio_write(codec, 0x18a020, 0x00000083);
+
+ snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53);
+
+ ca0132_clear_unsolicited(codec);
+ sbz_set_pin_ctl_default(codec);
+ snd_hda_codec_write(codec, 0x0B, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+
+ ca0113_mmio_gpio_set(codec, 5, false);
+ ca0113_mmio_gpio_set(codec, 2, false);
+ ca0113_mmio_gpio_set(codec, 3, false);
+ ca0113_mmio_gpio_set(codec, 0, false);
+ ca0113_mmio_gpio_set(codec, 4, true);
+ ca0113_mmio_gpio_set(codec, 0, true);
+ ca0113_mmio_gpio_set(codec, 5, true);
+ ca0113_mmio_gpio_set(codec, 2, false);
+ ca0113_mmio_gpio_set(codec, 3, false);
+}
+
+static void ca0132_exit_chip(struct hda_codec *codec)
+{
+ /* put any chip cleanup stuffs here. */
+
+ if (dspload_is_loaded(codec))
+ dsp_reset(codec);
+}
+
+/*
+ * This fixes a problem that was hard to reproduce. Very rarely, I would
+ * boot up, and there would be no sound, but the DSP indicated it had loaded
+ * properly. I did a few memory dumps to see if anything was different, and
+ * there were a few areas of memory uninitialized with a1a2a3a4. This function
+ * checks if those areas are uninitialized, and if they are, it'll attempt to
+ * reload the card 3 times. Usually it fixes by the second.
+ */
+static void sbz_dsp_startup_check(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int dsp_data_check[4];
+ unsigned int cur_address = 0x390;
+ unsigned int i;
+ unsigned int failure = 0;
+ unsigned int reload = 3;
+
+ if (spec->startup_check_entered)
+ return;
+
+ spec->startup_check_entered = true;
+
+ for (i = 0; i < 4; i++) {
+ chipio_read(codec, cur_address, &dsp_data_check[i]);
+ cur_address += 0x4;
+ }
+ for (i = 0; i < 4; i++) {
+ if (dsp_data_check[i] == 0xa1a2a3a4)
+ failure = 1;
+ }
+
+ codec_dbg(codec, "Startup Check: %d ", failure);
+ if (failure)
+ codec_info(codec, "DSP not initialized properly. Attempting to fix.");
+ /*
+ * While the failure condition is true, and we haven't reached our
+ * three reload limit, continue trying to reload the driver and
+ * fix the issue.
+ */
+ while (failure && (reload != 0)) {
+ codec_info(codec, "Reloading... Tries left: %d", reload);
+ sbz_exit_chip(codec);
+ spec->dsp_state = DSP_DOWNLOAD_INIT;
+ codec->patch_ops.init(codec);
+ failure = 0;
+ for (i = 0; i < 4; i++) {
+ chipio_read(codec, cur_address, &dsp_data_check[i]);
+ cur_address += 0x4;
+ }
+ for (i = 0; i < 4; i++) {
+ if (dsp_data_check[i] == 0xa1a2a3a4)
+ failure = 1;
+ }
+ reload--;
+ }
+
+ if (!failure && reload < 3)
+ codec_info(codec, "DSP fixed.");
+
+ if (!failure)
+ return;
+
+ codec_info(codec, "DSP failed to initialize properly. Either try a full shutdown or a suspend to clear the internal memory.");
+}
+
+/*
+ * This is for the extra volume verbs 0x797 (left) and 0x798 (right). These add
+ * extra precision for decibel values. If you had the dB value in floating point
+ * you would take the value after the decimal point, multiply by 64, and divide
+ * by 2. So for 8.59, it's (59 * 64) / 100. Useful if someone wanted to
+ * implement fixed point or floating point dB volumes. For now, I'll set them
+ * to 0 just incase a value has lingered from a boot into Windows.
+ */
+static void ca0132_alt_vol_setup(struct hda_codec *codec)
+{
+ snd_hda_codec_write(codec, 0x02, 0, 0x797, 0x00);
+ snd_hda_codec_write(codec, 0x02, 0, 0x798, 0x00);
+ snd_hda_codec_write(codec, 0x03, 0, 0x797, 0x00);
+ snd_hda_codec_write(codec, 0x03, 0, 0x798, 0x00);
+ snd_hda_codec_write(codec, 0x04, 0, 0x797, 0x00);
+ snd_hda_codec_write(codec, 0x04, 0, 0x798, 0x00);
+ snd_hda_codec_write(codec, 0x07, 0, 0x797, 0x00);
+ snd_hda_codec_write(codec, 0x07, 0, 0x798, 0x00);
+}
+
+/*
+ * Extra commands that don't really fit anywhere else.
+ */
+static void sbz_pre_dsp_setup(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ writel(0x00820680, spec->mem_base + 0x01C);
+ writel(0x00820680, spec->mem_base + 0x01C);
+
+ chipio_write(codec, 0x18b0a4, 0x000000c2);
+
+ snd_hda_codec_write(codec, 0x11, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x44);
+}
+
+static void r3d_pre_dsp_setup(struct hda_codec *codec)
+{
+ chipio_write(codec, 0x18b0a4, 0x000000c2);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x1E);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x1C);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x5B);
+
+ snd_hda_codec_write(codec, 0x11, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x44);
+}
+
+static void r3di_pre_dsp_setup(struct hda_codec *codec)
+{
+ chipio_write(codec, 0x18b0a4, 0x000000c2);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x1E);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x1C);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x5B);
+
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x20);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x19);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x00);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_DATA_WRITE, 0x40);
+
+ snd_hda_codec_write(codec, 0x11, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x04);
+}
+
+/*
+ * These are sent before the DSP is downloaded. Not sure
+ * what they do, or if they're necessary. Could possibly
+ * be removed. Figure they're better to leave in.
+ */
+static const unsigned int ca0113_mmio_init_address_sbz[] = {
+ 0x400, 0x408, 0x40c, 0x01c, 0xc0c, 0xc00, 0xc04, 0xc0c, 0xc0c, 0xc0c,
+ 0xc0c, 0xc08, 0xc08, 0xc08, 0xc08, 0xc08, 0xc04
+};
+
+static const unsigned int ca0113_mmio_init_data_sbz[] = {
+ 0x00000030, 0x00000000, 0x00000003, 0x00000003, 0x00000003,
+ 0x00000003, 0x000000c1, 0x000000f1, 0x00000001, 0x000000c7,
+ 0x000000c1, 0x00000080
+};
+
+static const unsigned int ca0113_mmio_init_data_zxr[] = {
+ 0x00000030, 0x00000000, 0x00000000, 0x00000003, 0x00000003,
+ 0x00000003, 0x00000001, 0x000000f1, 0x00000001, 0x000000c7,
+ 0x000000c1, 0x00000080
+};
+
+static const unsigned int ca0113_mmio_init_address_ae5[] = {
+ 0x400, 0x42c, 0x46c, 0x4ac, 0x4ec, 0x43c, 0x47c, 0x4bc, 0x4fc, 0x408,
+ 0x100, 0x410, 0x40c, 0x100, 0x100, 0x830, 0x86c, 0x800, 0x86c, 0x800,
+ 0x804, 0x20c, 0x01c, 0xc0c, 0xc00, 0xc04, 0xc0c, 0xc0c, 0xc0c, 0xc0c,
+ 0xc08, 0xc08, 0xc08, 0xc08, 0xc08, 0xc04, 0x01c
+};
+
+static const unsigned int ca0113_mmio_init_data_ae5[] = {
+ 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
+ 0x00000600, 0x00000014, 0x00000001, 0x0000060f, 0x0000070f,
+ 0x00000aff, 0x00000000, 0x0000006b, 0x00000001, 0x0000006b,
+ 0x00000057, 0x00800000, 0x00880680, 0x00000080, 0x00000030,
+ 0x00000000, 0x00000000, 0x00000003, 0x00000003, 0x00000003,
+ 0x00000001, 0x000000f1, 0x00000001, 0x000000c7, 0x000000c1,
+ 0x00000080, 0x00880680
+};
+
+static void ca0132_mmio_init_sbz(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int tmp[2], i, count, cur_addr;
+ const unsigned int *addr, *data;
+
+ addr = ca0113_mmio_init_address_sbz;
+ for (i = 0; i < 3; i++)
+ writel(0x00000000, spec->mem_base + addr[i]);
+
+ cur_addr = i;
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_ZXR:
+ tmp[0] = 0x00880480;
+ tmp[1] = 0x00000080;
+ break;
+ case QUIRK_SBZ:
+ tmp[0] = 0x00820680;
+ tmp[1] = 0x00000083;
+ break;
+ case QUIRK_R3D:
+ tmp[0] = 0x00880680;
+ tmp[1] = 0x00000083;
+ break;
+ default:
+ tmp[0] = 0x00000000;
+ tmp[1] = 0x00000000;
+ break;
+ }
+
+ for (i = 0; i < 2; i++)
+ writel(tmp[i], spec->mem_base + addr[cur_addr + i]);
+
+ cur_addr += i;
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_ZXR:
+ count = ARRAY_SIZE(ca0113_mmio_init_data_zxr);
+ data = ca0113_mmio_init_data_zxr;
+ break;
+ default:
+ count = ARRAY_SIZE(ca0113_mmio_init_data_sbz);
+ data = ca0113_mmio_init_data_sbz;
+ break;
+ }
+
+ for (i = 0; i < count; i++)
+ writel(data[i], spec->mem_base + addr[cur_addr + i]);
+}
+
+static void ca0132_mmio_init_ae5(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ const unsigned int *addr, *data;
+ unsigned int i, count;
+
+ addr = ca0113_mmio_init_address_ae5;
+ data = ca0113_mmio_init_data_ae5;
+ count = ARRAY_SIZE(ca0113_mmio_init_data_ae5);
+
+ if (ca0132_quirk(spec) == QUIRK_AE7) {
+ writel(0x00000680, spec->mem_base + 0x1c);
+ writel(0x00880680, spec->mem_base + 0x1c);
+ }
+
+ for (i = 0; i < count; i++) {
+ /*
+ * AE-7 shares all writes with the AE-5, except that it writes
+ * a different value to 0x20c.
+ */
+ if (i == 21 && ca0132_quirk(spec) == QUIRK_AE7) {
+ writel(0x00800001, spec->mem_base + addr[i]);
+ continue;
+ }
+
+ writel(data[i], spec->mem_base + addr[i]);
+ }
+
+ if (ca0132_quirk(spec) == QUIRK_AE5)
+ writel(0x00880680, spec->mem_base + 0x1c);
+}
+
+static void ca0132_mmio_init(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_R3D:
+ case QUIRK_SBZ:
+ case QUIRK_ZXR:
+ ca0132_mmio_init_sbz(codec);
+ break;
+ case QUIRK_AE5:
+ ca0132_mmio_init_ae5(codec);
+ break;
+ default:
+ break;
+ }
+}
+
+static const unsigned int ca0132_ae5_register_set_addresses[] = {
+ 0x304, 0x304, 0x304, 0x304, 0x100, 0x304, 0x100, 0x304, 0x100, 0x304,
+ 0x100, 0x304, 0x86c, 0x800, 0x86c, 0x800, 0x804
+};
+
+static const unsigned char ca0132_ae5_register_set_data[] = {
+ 0x0f, 0x0e, 0x1f, 0x0c, 0x3f, 0x08, 0x7f, 0x00, 0xff, 0x00, 0x6b,
+ 0x01, 0x6b, 0x57
+};
+
+/*
+ * This function writes to some SFR's, does some region2 writes, and then
+ * eventually resets the codec with the 0x7ff verb. Not quite sure why it does
+ * what it does.
+ */
+static void ae5_register_set(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int count = ARRAY_SIZE(ca0132_ae5_register_set_addresses);
+ const unsigned int *addr = ca0132_ae5_register_set_addresses;
+ const unsigned char *data = ca0132_ae5_register_set_data;
+ unsigned int i, cur_addr;
+ unsigned char tmp[3];
+
+ if (ca0132_quirk(spec) == QUIRK_AE7) {
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x41);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc8);
+ }
+
+ chipio_8051_write_direct(codec, 0x93, 0x10);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x44);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0xc2);
+
+ if (ca0132_quirk(spec) == QUIRK_AE7) {
+ tmp[0] = 0x03;
+ tmp[1] = 0x03;
+ tmp[2] = 0x07;
+ } else {
+ tmp[0] = 0x0f;
+ tmp[1] = 0x0f;
+ tmp[2] = 0x0f;
+ }
+
+ for (i = cur_addr = 0; i < 3; i++, cur_addr++)
+ writeb(tmp[i], spec->mem_base + addr[cur_addr]);
+
+ /*
+ * First writes are in single bytes, final are in 4 bytes. So, we use
+ * writeb, then writel.
+ */
+ for (i = 0; cur_addr < 12; i++, cur_addr++)
+ writeb(data[i], spec->mem_base + addr[cur_addr]);
+
+ for (; cur_addr < count; i++, cur_addr++)
+ writel(data[i], spec->mem_base + addr[cur_addr]);
+
+ writel(0x00800001, spec->mem_base + 0x20c);
+
+ if (ca0132_quirk(spec) == QUIRK_AE7) {
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83);
+ ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f);
+ } else {
+ ca0113_mmio_command_set(codec, 0x30, 0x2d, 0x3f);
+ }
+
+ chipio_8051_write_direct(codec, 0x90, 0x00);
+ chipio_8051_write_direct(codec, 0x90, 0x10);
+
+ if (ca0132_quirk(spec) == QUIRK_AE5)
+ ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83);
+
+ chipio_write(codec, 0x18b0a4, 0x000000c2);
+
+ snd_hda_codec_write(codec, 0x01, 0, 0x7ff, 0x00);
+ snd_hda_codec_write(codec, 0x01, 0, 0x7ff, 0x00);
+}
+
+/*
+ * Extra init functions for alternative ca0132 codecs. Done
+ * here so they don't clutter up the main ca0132_init function
+ * anymore than they have to.
+ */
+static void ca0132_alt_init(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ ca0132_alt_vol_setup(codec);
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ codec_dbg(codec, "SBZ alt_init");
+ ca0132_gpio_init(codec);
+ sbz_pre_dsp_setup(codec);
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_sequence_write(codec, spec->desktop_init_verbs);
+ break;
+ case QUIRK_R3DI:
+ codec_dbg(codec, "R3DI alt_init");
+ ca0132_gpio_init(codec);
+ ca0132_gpio_setup(codec);
+ r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADING);
+ r3di_pre_dsp_setup(codec);
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x6FF, 0xC4);
+ break;
+ case QUIRK_R3D:
+ r3d_pre_dsp_setup(codec);
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_sequence_write(codec, spec->desktop_init_verbs);
+ break;
+ case QUIRK_AE5:
+ ca0132_gpio_init(codec);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x49);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0x88);
+ chipio_write(codec, 0x18b030, 0x00000020);
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_sequence_write(codec, spec->desktop_init_verbs);
+ ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f);
+ break;
+ case QUIRK_AE7:
+ ca0132_gpio_init(codec);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x49);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PLL_PMU_WRITE, 0x88);
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_sequence_write(codec, spec->desktop_init_verbs);
+ chipio_write(codec, 0x18b008, 0x000000f8);
+ chipio_write(codec, 0x18b008, 0x000000f0);
+ chipio_write(codec, 0x18b030, 0x00000020);
+ ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f);
+ break;
+ case QUIRK_ZXR:
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_sequence_write(codec, spec->desktop_init_verbs);
+ break;
+ default:
+ break;
+ }
+}
+
+static int ca0132_init(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i;
+ bool dsp_loaded;
+
+ /*
+ * If the DSP is already downloaded, and init has been entered again,
+ * there's only two reasons for it. One, the codec has awaken from a
+ * suspended state, and in that case dspload_is_loaded will return
+ * false, and the init will be ran again. The other reason it gets
+ * re entered is on startup for some reason it triggers a suspend and
+ * resume state. In this case, it will check if the DSP is downloaded,
+ * and not run the init function again. For codecs using alt_functions,
+ * it will check if the DSP is loaded properly.
+ */
+ if (spec->dsp_state == DSP_DOWNLOADED) {
+ dsp_loaded = dspload_is_loaded(codec);
+ if (!dsp_loaded) {
+ spec->dsp_reload = true;
+ spec->dsp_state = DSP_DOWNLOAD_INIT;
+ } else {
+ if (ca0132_quirk(spec) == QUIRK_SBZ)
+ sbz_dsp_startup_check(codec);
+ return 0;
+ }
+ }
+
+ if (spec->dsp_state != DSP_DOWNLOAD_FAILED)
+ spec->dsp_state = DSP_DOWNLOAD_INIT;
+ spec->curr_chip_addx = INVALID_CHIP_ADDRESS;
+
+ if (ca0132_use_pci_mmio(spec))
+ ca0132_mmio_init(codec);
+
+ snd_hda_power_up_pm(codec);
+
+ if (ca0132_quirk(spec) == QUIRK_AE5 || ca0132_quirk(spec) == QUIRK_AE7)
+ ae5_register_set(codec);
+
+ ca0132_init_unsol(codec);
+ ca0132_init_params(codec);
+ ca0132_init_flags(codec);
+
+ snd_hda_sequence_write(codec, spec->base_init_verbs);
+
+ if (ca0132_use_alt_functions(spec))
+ ca0132_alt_init(codec);
+
+ ca0132_download_dsp(codec);
+
+ ca0132_refresh_widget_caps(codec);
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_R3DI:
+ case QUIRK_R3D:
+ r3d_setup_defaults(codec);
+ break;
+ case QUIRK_SBZ:
+ case QUIRK_ZXR:
+ sbz_setup_defaults(codec);
+ break;
+ case QUIRK_AE5:
+ ae5_setup_defaults(codec);
+ break;
+ case QUIRK_AE7:
+ ae7_setup_defaults(codec);
+ break;
+ default:
+ ca0132_setup_defaults(codec);
+ ca0132_init_analog_mic2(codec);
+ ca0132_init_dmic(codec);
+ break;
+ }
+
+ for (i = 0; i < spec->num_outputs; i++)
+ init_output(codec, spec->out_pins[i], spec->dacs[0]);
+
+ init_output(codec, cfg->dig_out_pins[0], spec->dig_out);
+
+ for (i = 0; i < spec->num_inputs; i++)
+ init_input(codec, spec->input_pins[i], spec->adcs[i]);
+
+ init_input(codec, cfg->dig_in_pin, spec->dig_in);
+
+ if (!ca0132_use_alt_functions(spec)) {
+ snd_hda_sequence_write(codec, spec->chip_init_verbs);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_EX_ID_SET, 0x0D);
+ snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0,
+ VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20);
+ }
+
+ if (ca0132_quirk(spec) == QUIRK_SBZ)
+ ca0132_gpio_setup(codec);
+
+ snd_hda_sequence_write(codec, spec->spec_init_verbs);
+ if (ca0132_use_alt_functions(spec)) {
+ ca0132_alt_select_out(codec);
+ ca0132_alt_select_in(codec);
+ } else {
+ ca0132_select_out(codec);
+ ca0132_select_mic(codec);
+ }
+
+ snd_hda_jack_report_sync(codec);
+
+ /*
+ * Re set the PlayEnhancement switch on a resume event, because the
+ * controls will not be reloaded.
+ */
+ if (spec->dsp_reload) {
+ spec->dsp_reload = false;
+ ca0132_pe_switch_set(codec);
+ }
+
+ snd_hda_power_down_pm(codec);
+
+ return 0;
+}
+
+static int dbpro_init(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int i;
+
+ init_output(codec, cfg->dig_out_pins[0], spec->dig_out);
+ init_input(codec, cfg->dig_in_pin, spec->dig_in);
+
+ for (i = 0; i < spec->num_inputs; i++)
+ init_input(codec, spec->input_pins[i], spec->adcs[i]);
+
+ return 0;
+}
+
+static void ca0132_free(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ cancel_delayed_work_sync(&spec->unsol_hp_work);
+ snd_hda_power_up(codec);
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ sbz_exit_chip(codec);
+ break;
+ case QUIRK_ZXR:
+ zxr_exit_chip(codec);
+ break;
+ case QUIRK_R3D:
+ r3d_exit_chip(codec);
+ break;
+ case QUIRK_AE5:
+ ae5_exit_chip(codec);
+ break;
+ case QUIRK_AE7:
+ ae7_exit_chip(codec);
+ break;
+ case QUIRK_R3DI:
+ r3di_gpio_shutdown(codec);
+ break;
+ default:
+ break;
+ }
+
+ snd_hda_sequence_write(codec, spec->base_exit_verbs);
+ ca0132_exit_chip(codec);
+
+ snd_hda_power_down(codec);
+#ifdef CONFIG_PCI
+ if (spec->mem_base)
+ pci_iounmap(codec->bus->pci, spec->mem_base);
+#endif
+ kfree(spec->spec_init_verbs);
+ kfree(codec->spec);
+}
+
+static void dbpro_free(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ zxr_dbpro_power_state_shutdown(codec);
+
+ kfree(spec->spec_init_verbs);
+ kfree(codec->spec);
+}
+
+static void ca0132_reboot_notify(struct hda_codec *codec)
+{
+ codec->patch_ops.free(codec);
+}
+
+#ifdef CONFIG_PM
+static int ca0132_suspend(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ cancel_delayed_work_sync(&spec->unsol_hp_work);
+ return 0;
+}
+#endif
+
+static const struct hda_codec_ops ca0132_patch_ops = {
+ .build_controls = ca0132_build_controls,
+ .build_pcms = ca0132_build_pcms,
+ .init = ca0132_init,
+ .free = ca0132_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .suspend = ca0132_suspend,
+#endif
+ .reboot_notify = ca0132_reboot_notify,
+};
+
+static const struct hda_codec_ops dbpro_patch_ops = {
+ .build_controls = dbpro_build_controls,
+ .build_pcms = dbpro_build_pcms,
+ .init = dbpro_init,
+ .free = dbpro_free,
+};
+
+static void ca0132_config(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ spec->dacs[0] = 0x2;
+ spec->dacs[1] = 0x3;
+ spec->dacs[2] = 0x4;
+
+ spec->multiout.dac_nids = spec->dacs;
+ spec->multiout.num_dacs = 3;
+
+ if (!ca0132_use_alt_functions(spec))
+ spec->multiout.max_channels = 2;
+ else
+ spec->multiout.max_channels = 6;
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_ALIENWARE:
+ codec_dbg(codec, "%s: QUIRK_ALIENWARE applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, alienware_pincfgs);
+ break;
+ case QUIRK_SBZ:
+ codec_dbg(codec, "%s: QUIRK_SBZ applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, sbz_pincfgs);
+ break;
+ case QUIRK_ZXR:
+ codec_dbg(codec, "%s: QUIRK_ZXR applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, zxr_pincfgs);
+ break;
+ case QUIRK_R3D:
+ codec_dbg(codec, "%s: QUIRK_R3D applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, r3d_pincfgs);
+ break;
+ case QUIRK_R3DI:
+ codec_dbg(codec, "%s: QUIRK_R3DI applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, r3di_pincfgs);
+ break;
+ case QUIRK_AE5:
+ codec_dbg(codec, "%s: QUIRK_AE5 applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, ae5_pincfgs);
+ break;
+ case QUIRK_AE7:
+ codec_dbg(codec, "%s: QUIRK_AE7 applied.\n", __func__);
+ snd_hda_apply_pincfgs(codec, ae7_pincfgs);
+ break;
+ default:
+ break;
+ }
+
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_ALIENWARE:
+ spec->num_outputs = 2;
+ spec->out_pins[0] = 0x0b; /* speaker out */
+ spec->out_pins[1] = 0x0f;
+ spec->shared_out_nid = 0x2;
+ spec->unsol_tag_hp = 0x0f;
+
+ spec->adcs[0] = 0x7; /* digital mic / analog mic1 */
+ spec->adcs[1] = 0x8; /* analog mic2 */
+ spec->adcs[2] = 0xa; /* what u hear */
+
+ spec->num_inputs = 3;
+ spec->input_pins[0] = 0x12;
+ spec->input_pins[1] = 0x11;
+ spec->input_pins[2] = 0x13;
+ spec->shared_mic_nid = 0x7;
+ spec->unsol_tag_amic1 = 0x11;
+ break;
+ case QUIRK_SBZ:
+ case QUIRK_R3D:
+ spec->num_outputs = 2;
+ spec->out_pins[0] = 0x0B; /* Line out */
+ spec->out_pins[1] = 0x0F; /* Rear headphone out */
+ spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/
+ spec->out_pins[3] = 0x11; /* Rear surround */
+ spec->shared_out_nid = 0x2;
+ spec->unsol_tag_hp = spec->out_pins[1];
+ spec->unsol_tag_front_hp = spec->out_pins[2];
+
+ spec->adcs[0] = 0x7; /* Rear Mic / Line-in */
+ spec->adcs[1] = 0x8; /* Front Mic, but only if no DSP */
+ spec->adcs[2] = 0xa; /* what u hear */
+
+ spec->num_inputs = 2;
+ spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */
+ spec->input_pins[1] = 0x13; /* What U Hear */
+ spec->shared_mic_nid = 0x7;
+ spec->unsol_tag_amic1 = spec->input_pins[0];
+
+ /* SPDIF I/O */
+ spec->dig_out = 0x05;
+ spec->multiout.dig_out_nid = spec->dig_out;
+ spec->dig_in = 0x09;
+ break;
+ case QUIRK_ZXR:
+ spec->num_outputs = 2;
+ spec->out_pins[0] = 0x0B; /* Line out */
+ spec->out_pins[1] = 0x0F; /* Rear headphone out */
+ spec->out_pins[2] = 0x10; /* Center/LFE */
+ spec->out_pins[3] = 0x11; /* Rear surround */
+ spec->shared_out_nid = 0x2;
+ spec->unsol_tag_hp = spec->out_pins[1];
+ spec->unsol_tag_front_hp = spec->out_pins[2];
+
+ spec->adcs[0] = 0x7; /* Rear Mic / Line-in */
+ spec->adcs[1] = 0x8; /* Not connected, no front mic */
+ spec->adcs[2] = 0xa; /* what u hear */
+
+ spec->num_inputs = 2;
+ spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */
+ spec->input_pins[1] = 0x13; /* What U Hear */
+ spec->shared_mic_nid = 0x7;
+ spec->unsol_tag_amic1 = spec->input_pins[0];
+ break;
+ case QUIRK_ZXR_DBPRO:
+ spec->adcs[0] = 0x8; /* ZxR DBPro Aux In */
+
+ spec->num_inputs = 1;
+ spec->input_pins[0] = 0x11; /* RCA Line-in */
+
+ spec->dig_out = 0x05;
+ spec->multiout.dig_out_nid = spec->dig_out;
+
+ spec->dig_in = 0x09;
+ break;
+ case QUIRK_AE5:
+ case QUIRK_AE7:
+ spec->num_outputs = 2;
+ spec->out_pins[0] = 0x0B; /* Line out */
+ spec->out_pins[1] = 0x11; /* Rear headphone out */
+ spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/
+ spec->out_pins[3] = 0x0F; /* Rear surround */
+ spec->shared_out_nid = 0x2;
+ spec->unsol_tag_hp = spec->out_pins[1];
+ spec->unsol_tag_front_hp = spec->out_pins[2];
+
+ spec->adcs[0] = 0x7; /* Rear Mic / Line-in */
+ spec->adcs[1] = 0x8; /* Front Mic, but only if no DSP */
+ spec->adcs[2] = 0xa; /* what u hear */
+
+ spec->num_inputs = 2;
+ spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */
+ spec->input_pins[1] = 0x13; /* What U Hear */
+ spec->shared_mic_nid = 0x7;
+ spec->unsol_tag_amic1 = spec->input_pins[0];
+
+ /* SPDIF I/O */
+ spec->dig_out = 0x05;
+ spec->multiout.dig_out_nid = spec->dig_out;
+ break;
+ case QUIRK_R3DI:
+ spec->num_outputs = 2;
+ spec->out_pins[0] = 0x0B; /* Line out */
+ spec->out_pins[1] = 0x0F; /* Rear headphone out */
+ spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/
+ spec->out_pins[3] = 0x11; /* Rear surround */
+ spec->shared_out_nid = 0x2;
+ spec->unsol_tag_hp = spec->out_pins[1];
+ spec->unsol_tag_front_hp = spec->out_pins[2];
+
+ spec->adcs[0] = 0x07; /* Rear Mic / Line-in */
+ spec->adcs[1] = 0x08; /* Front Mic, but only if no DSP */
+ spec->adcs[2] = 0x0a; /* what u hear */
+
+ spec->num_inputs = 2;
+ spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */
+ spec->input_pins[1] = 0x13; /* What U Hear */
+ spec->shared_mic_nid = 0x7;
+ spec->unsol_tag_amic1 = spec->input_pins[0];
+
+ /* SPDIF I/O */
+ spec->dig_out = 0x05;
+ spec->multiout.dig_out_nid = spec->dig_out;
+ break;
+ default:
+ spec->num_outputs = 2;
+ spec->out_pins[0] = 0x0b; /* speaker out */
+ spec->out_pins[1] = 0x10; /* headphone out */
+ spec->shared_out_nid = 0x2;
+ spec->unsol_tag_hp = spec->out_pins[1];
+
+ spec->adcs[0] = 0x7; /* digital mic / analog mic1 */
+ spec->adcs[1] = 0x8; /* analog mic2 */
+ spec->adcs[2] = 0xa; /* what u hear */
+
+ spec->num_inputs = 3;
+ spec->input_pins[0] = 0x12;
+ spec->input_pins[1] = 0x11;
+ spec->input_pins[2] = 0x13;
+ spec->shared_mic_nid = 0x7;
+ spec->unsol_tag_amic1 = spec->input_pins[0];
+
+ /* SPDIF I/O */
+ spec->dig_out = 0x05;
+ spec->multiout.dig_out_nid = spec->dig_out;
+ spec->dig_in = 0x09;
+ break;
+ }
+}
+
+static int ca0132_prepare_verbs(struct hda_codec *codec)
+{
+/* Verbs + terminator (an empty element) */
+#define NUM_SPEC_VERBS 2
+ struct ca0132_spec *spec = codec->spec;
+
+ spec->chip_init_verbs = ca0132_init_verbs0;
+ /*
+ * Since desktop cards use pci_mmio, this can be used to determine
+ * whether or not to use these verbs instead of a separate bool.
+ */
+ if (ca0132_use_pci_mmio(spec))
+ spec->desktop_init_verbs = ca0132_init_verbs1;
+ spec->spec_init_verbs = kcalloc(NUM_SPEC_VERBS,
+ sizeof(struct hda_verb),
+ GFP_KERNEL);
+ if (!spec->spec_init_verbs)
+ return -ENOMEM;
+
+ /* config EAPD */
+ spec->spec_init_verbs[0].nid = 0x0b;
+ spec->spec_init_verbs[0].param = 0x78D;
+ spec->spec_init_verbs[0].verb = 0x00;
+
+ /* Previously commented configuration */
+ /*
+ spec->spec_init_verbs[2].nid = 0x0b;
+ spec->spec_init_verbs[2].param = AC_VERB_SET_EAPD_BTLENABLE;
+ spec->spec_init_verbs[2].verb = 0x02;
+
+ spec->spec_init_verbs[3].nid = 0x10;
+ spec->spec_init_verbs[3].param = 0x78D;
+ spec->spec_init_verbs[3].verb = 0x02;
+
+ spec->spec_init_verbs[4].nid = 0x10;
+ spec->spec_init_verbs[4].param = AC_VERB_SET_EAPD_BTLENABLE;
+ spec->spec_init_verbs[4].verb = 0x02;
+ */
+
+ /* Terminator: spec->spec_init_verbs[NUM_SPEC_VERBS-1] */
+ return 0;
+}
+
+/*
+ * The Sound Blaster ZxR shares the same PCI subsystem ID as some regular
+ * Sound Blaster Z cards. However, they have different HDA codec subsystem
+ * ID's. So, we check for the ZxR's subsystem ID, as well as the DBPro
+ * daughter boards ID.
+ */
+static void sbz_detect_quirk(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+
+ switch (codec->core.subsystem_id) {
+ case 0x11020033:
+ spec->quirk = QUIRK_ZXR;
+ break;
+ case 0x1102003f:
+ spec->quirk = QUIRK_ZXR_DBPRO;
+ break;
+ default:
+ spec->quirk = QUIRK_SBZ;
+ break;
+ }
+}
+
+static int patch_ca0132(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec;
+ int err;
+ const struct snd_pci_quirk *quirk;
+
+ codec_dbg(codec, "patch_ca0132\n");
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ codec->spec = spec;
+ spec->codec = codec;
+
+ /* Detect codec quirk */
+ quirk = snd_pci_quirk_lookup(codec->bus->pci, ca0132_quirks);
+ if (quirk)
+ spec->quirk = quirk->value;
+ else
+ spec->quirk = QUIRK_NONE;
+ if (ca0132_quirk(spec) == QUIRK_SBZ)
+ sbz_detect_quirk(codec);
+
+ if (ca0132_quirk(spec) == QUIRK_ZXR_DBPRO)
+ codec->patch_ops = dbpro_patch_ops;
+ else
+ codec->patch_ops = ca0132_patch_ops;
+
+ codec->pcm_format_first = 1;
+ codec->no_sticky_stream = 1;
+
+
+ spec->dsp_state = DSP_DOWNLOAD_INIT;
+ spec->num_mixers = 1;
+
+ /* Set which mixers each quirk uses. */
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ spec->mixers[0] = desktop_mixer;
+ snd_hda_codec_set_name(codec, "Sound Blaster Z");
+ break;
+ case QUIRK_ZXR:
+ spec->mixers[0] = desktop_mixer;
+ snd_hda_codec_set_name(codec, "Sound Blaster ZxR");
+ break;
+ case QUIRK_ZXR_DBPRO:
+ break;
+ case QUIRK_R3D:
+ spec->mixers[0] = desktop_mixer;
+ snd_hda_codec_set_name(codec, "Recon3D");
+ break;
+ case QUIRK_R3DI:
+ spec->mixers[0] = r3di_mixer;
+ snd_hda_codec_set_name(codec, "Recon3Di");
+ break;
+ case QUIRK_AE5:
+ spec->mixers[0] = desktop_mixer;
+ snd_hda_codec_set_name(codec, "Sound BlasterX AE-5");
+ break;
+ case QUIRK_AE7:
+ spec->mixers[0] = desktop_mixer;
+ snd_hda_codec_set_name(codec, "Sound Blaster AE-7");
+ break;
+ default:
+ spec->mixers[0] = ca0132_mixer;
+ break;
+ }
+
+ /* Setup whether or not to use alt functions/controls/pci_mmio */
+ switch (ca0132_quirk(spec)) {
+ case QUIRK_SBZ:
+ case QUIRK_R3D:
+ case QUIRK_AE5:
+ case QUIRK_AE7:
+ case QUIRK_ZXR:
+ spec->use_alt_controls = true;
+ spec->use_alt_functions = true;
+ spec->use_pci_mmio = true;
+ break;
+ case QUIRK_R3DI:
+ spec->use_alt_controls = true;
+ spec->use_alt_functions = true;
+ spec->use_pci_mmio = false;
+ break;
+ default:
+ spec->use_alt_controls = false;
+ spec->use_alt_functions = false;
+ spec->use_pci_mmio = false;
+ break;
+ }
+
+#ifdef CONFIG_PCI
+ if (spec->use_pci_mmio) {
+ spec->mem_base = pci_iomap(codec->bus->pci, 2, 0xC20);
+ if (spec->mem_base == NULL) {
+ codec_warn(codec, "pci_iomap failed! Setting quirk to QUIRK_NONE.");
+ spec->quirk = QUIRK_NONE;
+ }
+ }
+#endif
+
+ spec->base_init_verbs = ca0132_base_init_verbs;
+ spec->base_exit_verbs = ca0132_base_exit_verbs;
+
+ INIT_DELAYED_WORK(&spec->unsol_hp_work, ca0132_unsol_hp_delayed);
+
+ ca0132_init_chip(codec);
+
+ ca0132_config(codec);
+
+ err = ca0132_prepare_verbs(codec);
+ if (err < 0)
+ goto error;
+
+ err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ ca0132_free(codec);
+ return err;
+}
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_ca0132[] = {
+ HDA_CODEC_ENTRY(0x11020011, "CA0132", patch_ca0132),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_ca0132);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Creative Sound Core3D codec");
+
+static struct hda_codec_driver ca0132_driver = {
+ .id = snd_hda_id_ca0132,
+};
+
+module_hda_codec_driver(ca0132_driver);
diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c
new file mode 100644
index 000000000..c10a264e9
--- /dev/null
+++ b/sound/pci/hda/patch_cirrus.c
@@ -0,0 +1,1244 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HD audio interface patch for Cirrus Logic CS420x chip
+ *
+ * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+/*
+ */
+
+struct cs_spec {
+ struct hda_gen_spec gen;
+
+ unsigned int gpio_mask;
+ unsigned int gpio_dir;
+ unsigned int gpio_data;
+ unsigned int gpio_eapd_hp; /* EAPD GPIO bit for headphones */
+ unsigned int gpio_eapd_speaker; /* EAPD GPIO bit for speakers */
+
+ /* CS421x */
+ unsigned int spdif_detect:1;
+ unsigned int spdif_present:1;
+ unsigned int sense_b:1;
+ hda_nid_t vendor_nid;
+
+ /* for MBP SPDIF control */
+ int (*spdif_sw_put)(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+};
+
+/* available models with CS420x */
+enum {
+ CS420X_MBP53,
+ CS420X_MBP55,
+ CS420X_IMAC27,
+ CS420X_GPIO_13,
+ CS420X_GPIO_23,
+ CS420X_MBP101,
+ CS420X_MBP81,
+ CS420X_MBA42,
+ CS420X_AUTO,
+ /* aliases */
+ CS420X_IMAC27_122 = CS420X_GPIO_23,
+ CS420X_APPLE = CS420X_GPIO_13,
+};
+
+/* CS421x boards */
+enum {
+ CS421X_CDB4210,
+ CS421X_SENSE_B,
+ CS421X_STUMPY,
+};
+
+/* Vendor-specific processing widget */
+#define CS420X_VENDOR_NID 0x11
+#define CS_DIG_OUT1_PIN_NID 0x10
+#define CS_DIG_OUT2_PIN_NID 0x15
+#define CS_DMIC1_PIN_NID 0x0e
+#define CS_DMIC2_PIN_NID 0x12
+
+/* coef indices */
+#define IDX_SPDIF_STAT 0x0000
+#define IDX_SPDIF_CTL 0x0001
+#define IDX_ADC_CFG 0x0002
+/* SZC bitmask, 4 modes below:
+ * 0 = immediate,
+ * 1 = digital immediate, analog zero-cross
+ * 2 = digtail & analog soft-ramp
+ * 3 = digital soft-ramp, analog zero-cross
+ */
+#define CS_COEF_ADC_SZC_MASK (3 << 0)
+#define CS_COEF_ADC_MIC_SZC_MODE (3 << 0) /* SZC setup for mic */
+#define CS_COEF_ADC_LI_SZC_MODE (3 << 0) /* SZC setup for line-in */
+/* PGA mode: 0 = differential, 1 = signle-ended */
+#define CS_COEF_ADC_MIC_PGA_MODE (1 << 5) /* PGA setup for mic */
+#define CS_COEF_ADC_LI_PGA_MODE (1 << 6) /* PGA setup for line-in */
+#define IDX_DAC_CFG 0x0003
+/* SZC bitmask, 4 modes below:
+ * 0 = Immediate
+ * 1 = zero-cross
+ * 2 = soft-ramp
+ * 3 = soft-ramp on zero-cross
+ */
+#define CS_COEF_DAC_HP_SZC_MODE (3 << 0) /* nid 0x02 */
+#define CS_COEF_DAC_LO_SZC_MODE (3 << 2) /* nid 0x03 */
+#define CS_COEF_DAC_SPK_SZC_MODE (3 << 4) /* nid 0x04 */
+
+#define IDX_BEEP_CFG 0x0004
+/* 0x0008 - test reg key */
+/* 0x0009 - 0x0014 -> 12 test regs */
+/* 0x0015 - visibility reg */
+
+/* Cirrus Logic CS4208 */
+#define CS4208_VENDOR_NID 0x24
+
+/*
+ * Cirrus Logic CS4210
+ *
+ * 1 DAC => HP(sense) / Speakers,
+ * 1 ADC <= LineIn(sense) / MicIn / DMicIn,
+ * 1 SPDIF OUT => SPDIF Trasmitter(sense)
+*/
+#define CS4210_DAC_NID 0x02
+#define CS4210_ADC_NID 0x03
+#define CS4210_VENDOR_NID 0x0B
+#define CS421X_DMIC_PIN_NID 0x09 /* Port E */
+#define CS421X_SPDIF_PIN_NID 0x0A /* Port H */
+
+#define CS421X_IDX_DEV_CFG 0x01
+#define CS421X_IDX_ADC_CFG 0x02
+#define CS421X_IDX_DAC_CFG 0x03
+#define CS421X_IDX_SPK_CTL 0x04
+
+/* Cirrus Logic CS4213 is like CS4210 but does not have SPDIF input/output */
+#define CS4213_VENDOR_NID 0x09
+
+
+static inline int cs_vendor_coef_get(struct hda_codec *codec, unsigned int idx)
+{
+ struct cs_spec *spec = codec->spec;
+ snd_hda_codec_write(codec, spec->vendor_nid, 0,
+ AC_VERB_SET_COEF_INDEX, idx);
+ return snd_hda_codec_read(codec, spec->vendor_nid, 0,
+ AC_VERB_GET_PROC_COEF, 0);
+}
+
+static inline void cs_vendor_coef_set(struct hda_codec *codec, unsigned int idx,
+ unsigned int coef)
+{
+ struct cs_spec *spec = codec->spec;
+ snd_hda_codec_write(codec, spec->vendor_nid, 0,
+ AC_VERB_SET_COEF_INDEX, idx);
+ snd_hda_codec_write(codec, spec->vendor_nid, 0,
+ AC_VERB_SET_PROC_COEF, coef);
+}
+
+/*
+ * auto-mute and auto-mic switching
+ * CS421x auto-output redirecting
+ * HP/SPK/SPDIF
+ */
+
+static void cs_automute(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+
+ /* mute HPs if spdif jack (SENSE_B) is present */
+ spec->gen.master_mute = !!(spec->spdif_present && spec->sense_b);
+
+ snd_hda_gen_update_outputs(codec);
+
+ if (spec->gpio_eapd_hp || spec->gpio_eapd_speaker) {
+ if (spec->gen.automute_speaker)
+ spec->gpio_data = spec->gen.hp_jack_present ?
+ spec->gpio_eapd_hp : spec->gpio_eapd_speaker;
+ else
+ spec->gpio_data =
+ spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, spec->gpio_data);
+ }
+}
+
+static bool is_active_pin(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int val;
+ val = snd_hda_codec_get_pincfg(codec, nid);
+ return (get_defcfg_connect(val) != AC_JACK_PORT_NONE);
+}
+
+static void init_input_coef(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+ unsigned int coef;
+
+ /* CS420x has multiple ADC, CS421x has single ADC */
+ if (spec->vendor_nid == CS420X_VENDOR_NID) {
+ coef = cs_vendor_coef_get(codec, IDX_BEEP_CFG);
+ if (is_active_pin(codec, CS_DMIC2_PIN_NID))
+ coef |= 1 << 4; /* DMIC2 2 chan on, GPIO1 off */
+ if (is_active_pin(codec, CS_DMIC1_PIN_NID))
+ coef |= 1 << 3; /* DMIC1 2 chan on, GPIO0 off
+ * No effect if SPDIF_OUT2 is
+ * selected in IDX_SPDIF_CTL.
+ */
+
+ cs_vendor_coef_set(codec, IDX_BEEP_CFG, coef);
+ }
+}
+
+static const struct hda_verb cs_coef_init_verbs[] = {
+ {0x11, AC_VERB_SET_PROC_STATE, 1},
+ {0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG},
+ {0x11, AC_VERB_SET_PROC_COEF,
+ (0x002a /* DAC1/2/3 SZCMode Soft Ramp */
+ | 0x0040 /* Mute DACs on FIFO error */
+ | 0x1000 /* Enable DACs High Pass Filter */
+ | 0x0400 /* Disable Coefficient Auto increment */
+ )},
+ /* ADC1/2 - Digital and Analog Soft Ramp */
+ {0x11, AC_VERB_SET_COEF_INDEX, IDX_ADC_CFG},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x000a},
+ /* Beep */
+ {0x11, AC_VERB_SET_COEF_INDEX, IDX_BEEP_CFG},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x0007}, /* Enable Beep thru DAC1/2/3 */
+
+ {} /* terminator */
+};
+
+static const struct hda_verb cs4208_coef_init_verbs[] = {
+ {0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */
+ {0x24, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */
+ {0x24, AC_VERB_SET_COEF_INDEX, 0x0033},
+ {0x24, AC_VERB_SET_PROC_COEF, 0x0001}, /* A1 ICS */
+ {0x24, AC_VERB_SET_COEF_INDEX, 0x0034},
+ {0x24, AC_VERB_SET_PROC_COEF, 0x1C01}, /* A1 Enable, A Thresh = 300mV */
+ {} /* terminator */
+};
+
+/* Errata: CS4207 rev C0/C1/C2 Silicon
+ *
+ * http://www.cirrus.com/en/pubs/errata/ER880C3.pdf
+ *
+ * 6. At high temperature (TA > +85°C), the digital supply current (IVD)
+ * may be excessive (up to an additional 200 μA), which is most easily
+ * observed while the part is being held in reset (RESET# active low).
+ *
+ * Root Cause: At initial powerup of the device, the logic that drives
+ * the clock and write enable to the S/PDIF SRC RAMs is not properly
+ * initialized.
+ * Certain random patterns will cause a steady leakage current in those
+ * RAM cells. The issue will resolve once the SRCs are used (turned on).
+ *
+ * Workaround: The following verb sequence briefly turns on the S/PDIF SRC
+ * blocks, which will alleviate the issue.
+ */
+
+static const struct hda_verb cs_errata_init_verbs[] = {
+ {0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */
+ {0x11, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */
+
+ {0x11, AC_VERB_SET_COEF_INDEX, 0x0008},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x9999},
+ {0x11, AC_VERB_SET_COEF_INDEX, 0x0017},
+ {0x11, AC_VERB_SET_PROC_COEF, 0xa412},
+ {0x11, AC_VERB_SET_COEF_INDEX, 0x0001},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x0009},
+
+ {0x07, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Rx: D0 */
+ {0x08, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Tx: D0 */
+
+ {0x11, AC_VERB_SET_COEF_INDEX, 0x0017},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x2412},
+ {0x11, AC_VERB_SET_COEF_INDEX, 0x0008},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x0000},
+ {0x11, AC_VERB_SET_COEF_INDEX, 0x0001},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x0008},
+ {0x11, AC_VERB_SET_PROC_STATE, 0x00},
+
+#if 0 /* Don't to set to D3 as we are in power-up sequence */
+ {0x07, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Rx: D3 */
+ {0x08, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Tx: D3 */
+ /*{0x01, AC_VERB_SET_POWER_STATE, 0x03},*/ /* AFG: D3 This is already handled */
+#endif
+
+ {} /* terminator */
+};
+
+/* SPDIF setup */
+static void init_digital_coef(struct hda_codec *codec)
+{
+ unsigned int coef;
+
+ coef = 0x0002; /* SRC_MUTE soft-mute on SPDIF (if no lock) */
+ coef |= 0x0008; /* Replace with mute on error */
+ if (is_active_pin(codec, CS_DIG_OUT2_PIN_NID))
+ coef |= 0x4000; /* RX to TX1 or TX2 Loopthru / SPDIF2
+ * SPDIF_OUT2 is shared with GPIO1 and
+ * DMIC_SDA2.
+ */
+ cs_vendor_coef_set(codec, IDX_SPDIF_CTL, coef);
+}
+
+static int cs_init(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+
+ if (spec->vendor_nid == CS420X_VENDOR_NID) {
+ /* init_verb sequence for C0/C1/C2 errata*/
+ snd_hda_sequence_write(codec, cs_errata_init_verbs);
+ snd_hda_sequence_write(codec, cs_coef_init_verbs);
+ } else if (spec->vendor_nid == CS4208_VENDOR_NID) {
+ snd_hda_sequence_write(codec, cs4208_coef_init_verbs);
+ }
+
+ snd_hda_gen_init(codec);
+
+ if (spec->gpio_mask) {
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
+ spec->gpio_mask);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
+ spec->gpio_dir);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+ spec->gpio_data);
+ }
+
+ if (spec->vendor_nid == CS420X_VENDOR_NID) {
+ init_input_coef(codec);
+ init_digital_coef(codec);
+ }
+
+ return 0;
+}
+
+static int cs_build_controls(struct hda_codec *codec)
+{
+ int err;
+
+ err = snd_hda_gen_build_controls(codec);
+ if (err < 0)
+ return err;
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD);
+ return 0;
+}
+
+#define cs_free snd_hda_gen_free
+
+static const struct hda_codec_ops cs_patch_ops = {
+ .build_controls = cs_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = cs_init,
+ .free = cs_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+};
+
+static int cs_parse_auto_config(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+ int err;
+ int i;
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
+ if (err < 0)
+ return err;
+
+ /* keep the ADCs powered up when it's dynamically switchable */
+ if (spec->gen.dyn_adc_switch) {
+ unsigned int done = 0;
+ for (i = 0; i < spec->gen.input_mux.num_items; i++) {
+ int idx = spec->gen.dyn_adc_idx[i];
+ if (done & (1 << idx))
+ continue;
+ snd_hda_gen_fix_pin_power(codec,
+ spec->gen.adc_nids[idx]);
+ done |= 1 << idx;
+ }
+ }
+
+ return 0;
+}
+
+static const struct hda_model_fixup cs420x_models[] = {
+ { .id = CS420X_MBP53, .name = "mbp53" },
+ { .id = CS420X_MBP55, .name = "mbp55" },
+ { .id = CS420X_IMAC27, .name = "imac27" },
+ { .id = CS420X_IMAC27_122, .name = "imac27_122" },
+ { .id = CS420X_APPLE, .name = "apple" },
+ { .id = CS420X_MBP101, .name = "mbp101" },
+ { .id = CS420X_MBP81, .name = "mbp81" },
+ { .id = CS420X_MBA42, .name = "mba42" },
+ {}
+};
+
+static const struct snd_pci_quirk cs420x_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x10de, 0x0ac0, "MacBookPro 5,3", CS420X_MBP53),
+ SND_PCI_QUIRK(0x10de, 0x0d94, "MacBookAir 3,1(2)", CS420X_MBP55),
+ SND_PCI_QUIRK(0x10de, 0xcb79, "MacBookPro 5,5", CS420X_MBP55),
+ SND_PCI_QUIRK(0x10de, 0xcb89, "MacBookPro 7,1", CS420X_MBP55),
+ /* this conflicts with too many other models */
+ /*SND_PCI_QUIRK(0x8086, 0x7270, "IMac 27 Inch", CS420X_IMAC27),*/
+
+ /* codec SSID */
+ SND_PCI_QUIRK(0x106b, 0x0600, "iMac 14,1", CS420X_IMAC27_122),
+ SND_PCI_QUIRK(0x106b, 0x0900, "iMac 12,1", CS420X_IMAC27_122),
+ SND_PCI_QUIRK(0x106b, 0x1c00, "MacBookPro 8,1", CS420X_MBP81),
+ SND_PCI_QUIRK(0x106b, 0x2000, "iMac 12,2", CS420X_IMAC27_122),
+ SND_PCI_QUIRK(0x106b, 0x2800, "MacBookPro 10,1", CS420X_MBP101),
+ SND_PCI_QUIRK(0x106b, 0x5600, "MacBookAir 5,2", CS420X_MBP81),
+ SND_PCI_QUIRK(0x106b, 0x5b00, "MacBookAir 4,2", CS420X_MBA42),
+ SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS420X_APPLE),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl mbp53_pincfgs[] = {
+ { 0x09, 0x012b4050 },
+ { 0x0a, 0x90100141 },
+ { 0x0b, 0x90100140 },
+ { 0x0c, 0x018b3020 },
+ { 0x0d, 0x90a00110 },
+ { 0x0e, 0x400000f0 },
+ { 0x0f, 0x01cbe030 },
+ { 0x10, 0x014be060 },
+ { 0x12, 0x400000f0 },
+ { 0x15, 0x400000f0 },
+ {} /* terminator */
+};
+
+static const struct hda_pintbl mbp55_pincfgs[] = {
+ { 0x09, 0x012b4030 },
+ { 0x0a, 0x90100121 },
+ { 0x0b, 0x90100120 },
+ { 0x0c, 0x400000f0 },
+ { 0x0d, 0x90a00110 },
+ { 0x0e, 0x400000f0 },
+ { 0x0f, 0x400000f0 },
+ { 0x10, 0x014be040 },
+ { 0x12, 0x400000f0 },
+ { 0x15, 0x400000f0 },
+ {} /* terminator */
+};
+
+static const struct hda_pintbl imac27_pincfgs[] = {
+ { 0x09, 0x012b4050 },
+ { 0x0a, 0x90100140 },
+ { 0x0b, 0x90100142 },
+ { 0x0c, 0x018b3020 },
+ { 0x0d, 0x90a00110 },
+ { 0x0e, 0x400000f0 },
+ { 0x0f, 0x01cbe030 },
+ { 0x10, 0x014be060 },
+ { 0x12, 0x01ab9070 },
+ { 0x15, 0x400000f0 },
+ {} /* terminator */
+};
+
+static const struct hda_pintbl mbp101_pincfgs[] = {
+ { 0x0d, 0x40ab90f0 },
+ { 0x0e, 0x90a600f0 },
+ { 0x12, 0x50a600f0 },
+ {} /* terminator */
+};
+
+static const struct hda_pintbl mba42_pincfgs[] = {
+ { 0x09, 0x012b4030 }, /* HP */
+ { 0x0a, 0x400000f0 },
+ { 0x0b, 0x90100120 }, /* speaker */
+ { 0x0c, 0x400000f0 },
+ { 0x0d, 0x90a00110 }, /* mic */
+ { 0x0e, 0x400000f0 },
+ { 0x0f, 0x400000f0 },
+ { 0x10, 0x400000f0 },
+ { 0x12, 0x400000f0 },
+ { 0x15, 0x400000f0 },
+ {} /* terminator */
+};
+
+static const struct hda_pintbl mba6_pincfgs[] = {
+ { 0x10, 0x032120f0 }, /* HP */
+ { 0x11, 0x500000f0 },
+ { 0x12, 0x90100010 }, /* Speaker */
+ { 0x13, 0x500000f0 },
+ { 0x14, 0x500000f0 },
+ { 0x15, 0x770000f0 },
+ { 0x16, 0x770000f0 },
+ { 0x17, 0x430000f0 },
+ { 0x18, 0x43ab9030 }, /* Mic */
+ { 0x19, 0x770000f0 },
+ { 0x1a, 0x770000f0 },
+ { 0x1b, 0x770000f0 },
+ { 0x1c, 0x90a00090 },
+ { 0x1d, 0x500000f0 },
+ { 0x1e, 0x500000f0 },
+ { 0x1f, 0x500000f0 },
+ { 0x20, 0x500000f0 },
+ { 0x21, 0x430000f0 },
+ { 0x22, 0x430000f0 },
+ {} /* terminator */
+};
+
+static void cs420x_fixup_gpio_13(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct cs_spec *spec = codec->spec;
+ spec->gpio_eapd_hp = 2; /* GPIO1 = headphones */
+ spec->gpio_eapd_speaker = 8; /* GPIO3 = speakers */
+ spec->gpio_mask = spec->gpio_dir =
+ spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
+ }
+}
+
+static void cs420x_fixup_gpio_23(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct cs_spec *spec = codec->spec;
+ spec->gpio_eapd_hp = 4; /* GPIO2 = headphones */
+ spec->gpio_eapd_speaker = 8; /* GPIO3 = speakers */
+ spec->gpio_mask = spec->gpio_dir =
+ spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
+ }
+}
+
+static const struct hda_fixup cs420x_fixups[] = {
+ [CS420X_MBP53] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = mbp53_pincfgs,
+ .chained = true,
+ .chain_id = CS420X_APPLE,
+ },
+ [CS420X_MBP55] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = mbp55_pincfgs,
+ .chained = true,
+ .chain_id = CS420X_GPIO_13,
+ },
+ [CS420X_IMAC27] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = imac27_pincfgs,
+ .chained = true,
+ .chain_id = CS420X_GPIO_13,
+ },
+ [CS420X_GPIO_13] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs420x_fixup_gpio_13,
+ },
+ [CS420X_GPIO_23] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs420x_fixup_gpio_23,
+ },
+ [CS420X_MBP101] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = mbp101_pincfgs,
+ .chained = true,
+ .chain_id = CS420X_GPIO_13,
+ },
+ [CS420X_MBP81] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* internal mic ADC2: right only, single ended */
+ {0x11, AC_VERB_SET_COEF_INDEX, IDX_ADC_CFG},
+ {0x11, AC_VERB_SET_PROC_COEF, 0x102a},
+ {}
+ },
+ .chained = true,
+ .chain_id = CS420X_GPIO_13,
+ },
+ [CS420X_MBA42] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = mba42_pincfgs,
+ .chained = true,
+ .chain_id = CS420X_GPIO_13,
+ },
+};
+
+static struct cs_spec *cs_alloc_spec(struct hda_codec *codec, int vendor_nid)
+{
+ struct cs_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return NULL;
+ codec->spec = spec;
+ spec->vendor_nid = vendor_nid;
+ codec->power_save_node = 1;
+ snd_hda_gen_spec_init(&spec->gen);
+
+ return spec;
+}
+
+static int patch_cs420x(struct hda_codec *codec)
+{
+ struct cs_spec *spec;
+ int err;
+
+ spec = cs_alloc_spec(codec, CS420X_VENDOR_NID);
+ if (!spec)
+ return -ENOMEM;
+
+ codec->patch_ops = cs_patch_ops;
+ spec->gen.automute_hook = cs_automute;
+ codec->single_adc_amp = 1;
+
+ snd_hda_pick_fixup(codec, cs420x_models, cs420x_fixup_tbl,
+ cs420x_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = cs_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ cs_free(codec);
+ return err;
+}
+
+/*
+ * CS4208 support:
+ * Its layout is no longer compatible with CS4206/CS4207
+ */
+enum {
+ CS4208_MAC_AUTO,
+ CS4208_MBA6,
+ CS4208_MBP11,
+ CS4208_MACMINI,
+ CS4208_GPIO0,
+};
+
+static const struct hda_model_fixup cs4208_models[] = {
+ { .id = CS4208_GPIO0, .name = "gpio0" },
+ { .id = CS4208_MBA6, .name = "mba6" },
+ { .id = CS4208_MBP11, .name = "mbp11" },
+ { .id = CS4208_MACMINI, .name = "macmini" },
+ {}
+};
+
+static const struct snd_pci_quirk cs4208_fixup_tbl[] = {
+ SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO),
+ {} /* terminator */
+};
+
+/* codec SSID matching */
+static const struct snd_pci_quirk cs4208_mac_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x106b, 0x5e00, "MacBookPro 11,2", CS4208_MBP11),
+ SND_PCI_QUIRK(0x106b, 0x6c00, "MacMini 7,1", CS4208_MACMINI),
+ SND_PCI_QUIRK(0x106b, 0x7100, "MacBookAir 6,1", CS4208_MBA6),
+ SND_PCI_QUIRK(0x106b, 0x7200, "MacBookAir 6,2", CS4208_MBA6),
+ SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11),
+ {} /* terminator */
+};
+
+static void cs4208_fixup_gpio0(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct cs_spec *spec = codec->spec;
+ spec->gpio_eapd_hp = 0;
+ spec->gpio_eapd_speaker = 1;
+ spec->gpio_mask = spec->gpio_dir =
+ spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
+ }
+}
+
+static const struct hda_fixup cs4208_fixups[];
+
+/* remap the fixup from codec SSID and apply it */
+static void cs4208_fixup_mac(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
+ snd_hda_pick_fixup(codec, NULL, cs4208_mac_fixup_tbl, cs4208_fixups);
+ if (codec->fixup_id == HDA_FIXUP_ID_NOT_SET)
+ codec->fixup_id = CS4208_GPIO0; /* default fixup */
+ snd_hda_apply_fixup(codec, action);
+}
+
+/* MacMini 7,1 has the inverted jack detection */
+static void cs4208_fixup_macmini(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x18, 0x00ab9150 }, /* mic (audio-in) jack: disable detect */
+ { 0x21, 0x004be140 }, /* SPDIF: disable detect */
+ { }
+ };
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ /* HP pin (0x10) has an inverted detection */
+ codec->inv_jack_detect = 1;
+ /* disable the bogus Mic and SPDIF jack detections */
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ }
+}
+
+static int cs4208_spdif_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs_spec *spec = codec->spec;
+ hda_nid_t pin = spec->gen.autocfg.dig_out_pins[0];
+ int pinctl = ucontrol->value.integer.value[0] ? PIN_OUT : 0;
+
+ snd_hda_set_pin_ctl_cache(codec, pin, pinctl);
+ return spec->spdif_sw_put(kcontrol, ucontrol);
+}
+
+/* hook the SPDIF switch */
+static void cs4208_fixup_spdif_switch(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_BUILD) {
+ struct cs_spec *spec = codec->spec;
+ struct snd_kcontrol *kctl;
+
+ if (!spec->gen.autocfg.dig_out_pins[0])
+ return;
+ kctl = snd_hda_find_mixer_ctl(codec, "IEC958 Playback Switch");
+ if (!kctl)
+ return;
+ spec->spdif_sw_put = kctl->put;
+ kctl->put = cs4208_spdif_sw_put;
+ }
+}
+
+static const struct hda_fixup cs4208_fixups[] = {
+ [CS4208_MBA6] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = mba6_pincfgs,
+ .chained = true,
+ .chain_id = CS4208_GPIO0,
+ },
+ [CS4208_MBP11] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs4208_fixup_spdif_switch,
+ .chained = true,
+ .chain_id = CS4208_GPIO0,
+ },
+ [CS4208_MACMINI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs4208_fixup_macmini,
+ .chained = true,
+ .chain_id = CS4208_GPIO0,
+ },
+ [CS4208_GPIO0] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs4208_fixup_gpio0,
+ },
+ [CS4208_MAC_AUTO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs4208_fixup_mac,
+ },
+};
+
+/* correct the 0dB offset of input pins */
+static void cs4208_fix_amp_caps(struct hda_codec *codec, hda_nid_t adc)
+{
+ unsigned int caps;
+
+ caps = query_amp_caps(codec, adc, HDA_INPUT);
+ caps &= ~(AC_AMPCAP_OFFSET);
+ caps |= 0x02;
+ snd_hda_override_amp_caps(codec, adc, HDA_INPUT, caps);
+}
+
+static int patch_cs4208(struct hda_codec *codec)
+{
+ struct cs_spec *spec;
+ int err;
+
+ spec = cs_alloc_spec(codec, CS4208_VENDOR_NID);
+ if (!spec)
+ return -ENOMEM;
+
+ codec->patch_ops = cs_patch_ops;
+ spec->gen.automute_hook = cs_automute;
+ /* exclude NID 0x10 (HP) from output volumes due to different steps */
+ spec->gen.out_vol_mask = 1ULL << 0x10;
+
+ snd_hda_pick_fixup(codec, cs4208_models, cs4208_fixup_tbl,
+ cs4208_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ snd_hda_override_wcaps(codec, 0x18,
+ get_wcaps(codec, 0x18) | AC_WCAP_STEREO);
+ cs4208_fix_amp_caps(codec, 0x18);
+ cs4208_fix_amp_caps(codec, 0x1b);
+ cs4208_fix_amp_caps(codec, 0x1c);
+
+ err = cs_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ cs_free(codec);
+ return err;
+}
+
+/*
+ * Cirrus Logic CS4210
+ *
+ * 1 DAC => HP(sense) / Speakers,
+ * 1 ADC <= LineIn(sense) / MicIn / DMicIn,
+ * 1 SPDIF OUT => SPDIF Trasmitter(sense)
+*/
+
+/* CS4210 board names */
+static const struct hda_model_fixup cs421x_models[] = {
+ { .id = CS421X_CDB4210, .name = "cdb4210" },
+ { .id = CS421X_STUMPY, .name = "stumpy" },
+ {}
+};
+
+static const struct snd_pci_quirk cs421x_fixup_tbl[] = {
+ /* Test Intel board + CDB2410 */
+ SND_PCI_QUIRK(0x8086, 0x5001, "DP45SG/CDB4210", CS421X_CDB4210),
+ {} /* terminator */
+};
+
+/* CS4210 board pinconfigs */
+/* Default CS4210 (CDB4210)*/
+static const struct hda_pintbl cdb4210_pincfgs[] = {
+ { 0x05, 0x0321401f },
+ { 0x06, 0x90170010 },
+ { 0x07, 0x03813031 },
+ { 0x08, 0xb7a70037 },
+ { 0x09, 0xb7a6003e },
+ { 0x0a, 0x034510f0 },
+ {} /* terminator */
+};
+
+/* Stumpy ChromeBox */
+static const struct hda_pintbl stumpy_pincfgs[] = {
+ { 0x05, 0x022120f0 },
+ { 0x06, 0x901700f0 },
+ { 0x07, 0x02a120f0 },
+ { 0x08, 0x77a70037 },
+ { 0x09, 0x77a6003e },
+ { 0x0a, 0x434510f0 },
+ {} /* terminator */
+};
+
+/* Setup GPIO/SENSE for each board (if used) */
+static void cs421x_fixup_sense_b(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct cs_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->sense_b = 1;
+}
+
+static const struct hda_fixup cs421x_fixups[] = {
+ [CS421X_CDB4210] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cdb4210_pincfgs,
+ .chained = true,
+ .chain_id = CS421X_SENSE_B,
+ },
+ [CS421X_SENSE_B] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs421x_fixup_sense_b,
+ },
+ [CS421X_STUMPY] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stumpy_pincfgs,
+ },
+};
+
+static const struct hda_verb cs421x_coef_init_verbs[] = {
+ {0x0B, AC_VERB_SET_PROC_STATE, 1},
+ {0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_DEV_CFG},
+ /*
+ Disable Coefficient Index Auto-Increment(DAI)=1,
+ PDREF=0
+ */
+ {0x0B, AC_VERB_SET_PROC_COEF, 0x0001 },
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_ADC_CFG},
+ /* ADC SZCMode = Digital Soft Ramp */
+ {0x0B, AC_VERB_SET_PROC_COEF, 0x0002 },
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_DAC_CFG},
+ {0x0B, AC_VERB_SET_PROC_COEF,
+ (0x0002 /* DAC SZCMode = Digital Soft Ramp */
+ | 0x0004 /* Mute DAC on FIFO error */
+ | 0x0008 /* Enable DAC High Pass Filter */
+ )},
+ {} /* terminator */
+};
+
+/* Errata: CS4210 rev A1 Silicon
+ *
+ * http://www.cirrus.com/en/pubs/errata/
+ *
+ * Description:
+ * 1. Performance degredation is present in the ADC.
+ * 2. Speaker output is not completely muted upon HP detect.
+ * 3. Noise is present when clipping occurs on the amplified
+ * speaker outputs.
+ *
+ * Workaround:
+ * The following verb sequence written to the registers during
+ * initialization will correct the issues listed above.
+ */
+
+static const struct hda_verb cs421x_coef_init_verbs_A1_silicon_fixes[] = {
+ {0x0B, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, 0x0006},
+ {0x0B, AC_VERB_SET_PROC_COEF, 0x9999}, /* Test mode: on */
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, 0x000A},
+ {0x0B, AC_VERB_SET_PROC_COEF, 0x14CB}, /* Chop double */
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, 0x0011},
+ {0x0B, AC_VERB_SET_PROC_COEF, 0xA2D0}, /* Increase ADC current */
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, 0x001A},
+ {0x0B, AC_VERB_SET_PROC_COEF, 0x02A9}, /* Mute speaker */
+
+ {0x0B, AC_VERB_SET_COEF_INDEX, 0x001B},
+ {0x0B, AC_VERB_SET_PROC_COEF, 0X1006}, /* Remove noise */
+
+ {} /* terminator */
+};
+
+/* Speaker Amp Gain is controlled by the vendor widget's coef 4 */
+static const DECLARE_TLV_DB_SCALE(cs421x_speaker_boost_db_scale, 900, 300, 0);
+
+static int cs421x_boost_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;
+ uinfo->value.integer.max = 3;
+ return 0;
+}
+
+static int cs421x_boost_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] =
+ cs_vendor_coef_get(codec, CS421X_IDX_SPK_CTL) & 0x0003;
+ return 0;
+}
+
+static int cs421x_boost_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ unsigned int vol = ucontrol->value.integer.value[0];
+ unsigned int coef =
+ cs_vendor_coef_get(codec, CS421X_IDX_SPK_CTL);
+ unsigned int original_coef = coef;
+
+ coef &= ~0x0003;
+ coef |= (vol & 0x0003);
+ if (original_coef == coef)
+ return 0;
+ else {
+ cs_vendor_coef_set(codec, CS421X_IDX_SPK_CTL, coef);
+ return 1;
+ }
+}
+
+static const struct snd_kcontrol_new cs421x_speaker_boost_ctl = {
+
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Speaker Boost Playback Volume",
+ .info = cs421x_boost_vol_info,
+ .get = cs421x_boost_vol_get,
+ .put = cs421x_boost_vol_put,
+ .tlv = { .p = cs421x_speaker_boost_db_scale },
+};
+
+static void cs4210_pinmux_init(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+ unsigned int def_conf, coef;
+
+ /* GPIO, DMIC_SCL, DMIC_SDA and SENSE_B are multiplexed */
+ coef = cs_vendor_coef_get(codec, CS421X_IDX_DEV_CFG);
+
+ if (spec->gpio_mask)
+ coef |= 0x0008; /* B1,B2 are GPIOs */
+ else
+ coef &= ~0x0008;
+
+ if (spec->sense_b)
+ coef |= 0x0010; /* B2 is SENSE_B, not inverted */
+ else
+ coef &= ~0x0010;
+
+ cs_vendor_coef_set(codec, CS421X_IDX_DEV_CFG, coef);
+
+ if ((spec->gpio_mask || spec->sense_b) &&
+ is_active_pin(codec, CS421X_DMIC_PIN_NID)) {
+
+ /*
+ GPIO or SENSE_B forced - disconnect the DMIC pin.
+ */
+ def_conf = snd_hda_codec_get_pincfg(codec, CS421X_DMIC_PIN_NID);
+ def_conf &= ~AC_DEFCFG_PORT_CONN;
+ def_conf |= (AC_JACK_PORT_NONE << AC_DEFCFG_PORT_CONN_SHIFT);
+ snd_hda_codec_set_pincfg(codec, CS421X_DMIC_PIN_NID, def_conf);
+ }
+}
+
+static void cs4210_spdif_automute(struct hda_codec *codec,
+ struct hda_jack_callback *tbl)
+{
+ struct cs_spec *spec = codec->spec;
+ bool spdif_present = false;
+ hda_nid_t spdif_pin = spec->gen.autocfg.dig_out_pins[0];
+
+ /* detect on spdif is specific to CS4210 */
+ if (!spec->spdif_detect ||
+ spec->vendor_nid != CS4210_VENDOR_NID)
+ return;
+
+ spdif_present = snd_hda_jack_detect(codec, spdif_pin);
+ if (spdif_present == spec->spdif_present)
+ return;
+
+ spec->spdif_present = spdif_present;
+ /* SPDIF TX on/off */
+ snd_hda_set_pin_ctl(codec, spdif_pin, spdif_present ? PIN_OUT : 0);
+
+ cs_automute(codec);
+}
+
+static void parse_cs421x_digital(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ int i;
+
+ for (i = 0; i < cfg->dig_outs; i++) {
+ hda_nid_t nid = cfg->dig_out_pins[i];
+ if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) {
+ spec->spdif_detect = 1;
+ snd_hda_jack_detect_enable_callback(codec, nid,
+ cs4210_spdif_automute);
+ }
+ }
+}
+
+static int cs421x_init(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+
+ if (spec->vendor_nid == CS4210_VENDOR_NID) {
+ snd_hda_sequence_write(codec, cs421x_coef_init_verbs);
+ snd_hda_sequence_write(codec, cs421x_coef_init_verbs_A1_silicon_fixes);
+ cs4210_pinmux_init(codec);
+ }
+
+ snd_hda_gen_init(codec);
+
+ if (spec->gpio_mask) {
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
+ spec->gpio_mask);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
+ spec->gpio_dir);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+ spec->gpio_data);
+ }
+
+ init_input_coef(codec);
+
+ cs4210_spdif_automute(codec, NULL);
+
+ return 0;
+}
+
+static void fix_volume_caps(struct hda_codec *codec, hda_nid_t dac)
+{
+ unsigned int caps;
+
+ /* set the upper-limit for mixer amp to 0dB */
+ caps = query_amp_caps(codec, dac, HDA_OUTPUT);
+ caps &= ~(0x7f << AC_AMPCAP_NUM_STEPS_SHIFT);
+ caps |= ((caps >> AC_AMPCAP_OFFSET_SHIFT) & 0x7f)
+ << AC_AMPCAP_NUM_STEPS_SHIFT;
+ snd_hda_override_amp_caps(codec, dac, HDA_OUTPUT, caps);
+}
+
+static int cs421x_parse_auto_config(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+ hda_nid_t dac = CS4210_DAC_NID;
+ int err;
+
+ fix_volume_caps(codec, dac);
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
+ if (err < 0)
+ return err;
+
+ parse_cs421x_digital(codec);
+
+ if (spec->gen.autocfg.speaker_outs &&
+ spec->vendor_nid == CS4210_VENDOR_NID) {
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL,
+ &cs421x_speaker_boost_ctl))
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ Manage PDREF, when transitioning to D3hot
+ (DAC,ADC) -> D3, PDREF=1, AFG->D3
+*/
+static int cs421x_suspend(struct hda_codec *codec)
+{
+ struct cs_spec *spec = codec->spec;
+ unsigned int coef;
+
+ snd_hda_shutup_pins(codec);
+
+ snd_hda_codec_write(codec, CS4210_DAC_NID, 0,
+ AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+ snd_hda_codec_write(codec, CS4210_ADC_NID, 0,
+ AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+
+ if (spec->vendor_nid == CS4210_VENDOR_NID) {
+ coef = cs_vendor_coef_get(codec, CS421X_IDX_DEV_CFG);
+ coef |= 0x0004; /* PDREF */
+ cs_vendor_coef_set(codec, CS421X_IDX_DEV_CFG, coef);
+ }
+
+ return 0;
+}
+#endif
+
+static const struct hda_codec_ops cs421x_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = cs421x_init,
+ .free = cs_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .suspend = cs421x_suspend,
+#endif
+};
+
+static int patch_cs4210(struct hda_codec *codec)
+{
+ struct cs_spec *spec;
+ int err;
+
+ spec = cs_alloc_spec(codec, CS4210_VENDOR_NID);
+ if (!spec)
+ return -ENOMEM;
+
+ codec->patch_ops = cs421x_patch_ops;
+ spec->gen.automute_hook = cs_automute;
+
+ snd_hda_pick_fixup(codec, cs421x_models, cs421x_fixup_tbl,
+ cs421x_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ /*
+ Update the GPIO/DMIC/SENSE_B pinmux before the configuration
+ is auto-parsed. If GPIO or SENSE_B is forced, DMIC input
+ is disabled.
+ */
+ cs4210_pinmux_init(codec);
+
+ err = cs421x_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ cs_free(codec);
+ return err;
+}
+
+static int patch_cs4213(struct hda_codec *codec)
+{
+ struct cs_spec *spec;
+ int err;
+
+ spec = cs_alloc_spec(codec, CS4213_VENDOR_NID);
+ if (!spec)
+ return -ENOMEM;
+
+ codec->patch_ops = cs421x_patch_ops;
+
+ err = cs421x_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ cs_free(codec);
+ return err;
+}
+
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_cirrus[] = {
+ HDA_CODEC_ENTRY(0x10134206, "CS4206", patch_cs420x),
+ HDA_CODEC_ENTRY(0x10134207, "CS4207", patch_cs420x),
+ HDA_CODEC_ENTRY(0x10134208, "CS4208", patch_cs4208),
+ HDA_CODEC_ENTRY(0x10134210, "CS4210", patch_cs4210),
+ HDA_CODEC_ENTRY(0x10134213, "CS4213", patch_cs4213),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cirrus);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cirrus Logic HD-audio codec");
+
+static struct hda_codec_driver cirrus_driver = {
+ .id = snd_hda_id_cirrus,
+};
+
+module_hda_codec_driver(cirrus_driver);
diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c
new file mode 100644
index 000000000..2ddd33f8d
--- /dev/null
+++ b/sound/pci/hda/patch_cmedia.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for C-Media CMI9880
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+struct cmi_spec {
+ struct hda_gen_spec gen;
+};
+
+/*
+ * stuff for auto-parser
+ */
+static const struct hda_codec_ops cmi_auto_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = snd_hda_gen_init,
+ .free = snd_hda_gen_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+};
+
+static int patch_cmi9880(struct hda_codec *codec)
+{
+ struct cmi_spec *spec;
+ struct auto_pin_cfg *cfg;
+ int err;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ codec->spec = spec;
+ codec->patch_ops = cmi_auto_patch_ops;
+ cfg = &spec->gen.autocfg;
+ snd_hda_gen_spec_init(&spec->gen);
+
+ err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0);
+ if (err < 0)
+ goto error;
+ err = snd_hda_gen_parse_auto_config(codec, cfg);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+static int patch_cmi8888(struct hda_codec *codec)
+{
+ struct cmi_spec *spec;
+ struct auto_pin_cfg *cfg;
+ int err;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+
+ codec->spec = spec;
+ codec->patch_ops = cmi_auto_patch_ops;
+ cfg = &spec->gen.autocfg;
+ snd_hda_gen_spec_init(&spec->gen);
+
+ /* mask NID 0x10 from the playback volume selection;
+ * it's a headphone boost volume handled manually below
+ */
+ spec->gen.out_vol_mask = (1ULL << 0x10);
+
+ err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0);
+ if (err < 0)
+ goto error;
+ err = snd_hda_gen_parse_auto_config(codec, cfg);
+ if (err < 0)
+ goto error;
+
+ if (get_defcfg_device(snd_hda_codec_get_pincfg(codec, 0x10)) ==
+ AC_JACK_HP_OUT) {
+ static const struct snd_kcontrol_new amp_kctl =
+ HDA_CODEC_VOLUME("Headphone Amp Playback Volume",
+ 0x10, 0, HDA_OUTPUT);
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &amp_kctl)) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+
+ return 0;
+
+ error:
+ snd_hda_gen_free(codec);
+ return err;
+}
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_cmedia[] = {
+ HDA_CODEC_ENTRY(0x13f68888, "CMI8888", patch_cmi8888),
+ HDA_CODEC_ENTRY(0x13f69880, "CMI9880", patch_cmi9880),
+ HDA_CODEC_ENTRY(0x434d4980, "CMI9880", patch_cmi9880),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cmedia);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("C-Media HD-audio codec");
+
+static struct hda_codec_driver cmedia_driver = {
+ .id = snd_hda_id_cmedia,
+};
+
+module_hda_codec_driver(cmedia_driver);
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
new file mode 100644
index 000000000..e35c470eb
--- /dev/null
+++ b/sound/pci/hda/patch_conexant.c
@@ -0,0 +1,1164 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HD audio interface patch for Conexant HDA audio codec
+ *
+ * Copyright (c) 2006 Pototskiy Akex <alex.pototskiy@gmail.com>
+ * Takashi Iwai <tiwai@suse.de>
+ * Tobin Davis <tdavis@dsl-only.net>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_beep.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+struct conexant_spec {
+ struct hda_gen_spec gen;
+
+ /* extra EAPD pins */
+ unsigned int num_eapds;
+ hda_nid_t eapds[4];
+ bool dynamic_eapd;
+ hda_nid_t mute_led_eapd;
+
+ unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */
+
+ /* OPLC XO specific */
+ bool recording;
+ bool dc_enable;
+ unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */
+ struct nid_path *dc_mode_path;
+
+ int mute_led_polarity;
+ unsigned int gpio_led;
+ unsigned int gpio_mute_led_mask;
+ unsigned int gpio_mic_led_mask;
+
+};
+
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* additional beep mixers; private_value will be overwritten */
+static const struct snd_kcontrol_new cxt_beep_mixer[] = {
+ HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT),
+};
+
+static int set_beep_amp(struct conexant_spec *spec, hda_nid_t nid,
+ int idx, int dir)
+{
+ struct snd_kcontrol_new *knew;
+ unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir);
+ int i;
+
+ spec->gen.beep_nid = nid;
+ for (i = 0; i < ARRAY_SIZE(cxt_beep_mixer); i++) {
+ knew = snd_hda_gen_add_kctl(&spec->gen, NULL,
+ &cxt_beep_mixer[i]);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = beep_amp;
+ }
+ return 0;
+}
+
+static int cx_auto_parse_beep(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec)
+ if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP)
+ return set_beep_amp(spec, nid, 0, HDA_OUTPUT);
+ return 0;
+}
+#else
+#define cx_auto_parse_beep(codec) 0
+#endif
+
+/*
+ * Automatic parser for CX20641 & co
+ */
+
+/* parse EAPDs */
+static void cx_auto_parse_eapd(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec) {
+ if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+ continue;
+ if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD))
+ continue;
+ spec->eapds[spec->num_eapds++] = nid;
+ if (spec->num_eapds >= ARRAY_SIZE(spec->eapds))
+ break;
+ }
+
+ /* NOTE: below is a wild guess; if we have more than two EAPDs,
+ * it's a new chip, where EAPDs are supposed to be associated to
+ * pins, and we can control EAPD per pin.
+ * OTOH, if only one or two EAPDs are found, it's an old chip,
+ * thus it might control over all pins.
+ */
+ if (spec->num_eapds > 2)
+ spec->dynamic_eapd = 1;
+}
+
+static void cx_auto_turn_eapd(struct hda_codec *codec, int num_pins,
+ const hda_nid_t *pins, bool on)
+{
+ int i;
+ for (i = 0; i < num_pins; i++) {
+ if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD)
+ snd_hda_codec_write(codec, pins[i], 0,
+ AC_VERB_SET_EAPD_BTLENABLE,
+ on ? 0x02 : 0);
+ }
+}
+
+/* turn on/off EAPD according to Master switch */
+static void cx_auto_vmaster_hook(void *private_data, int enabled)
+{
+ struct hda_codec *codec = private_data;
+ struct conexant_spec *spec = codec->spec;
+
+ cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, enabled);
+}
+
+/* turn on/off EAPD according to Master switch (inversely!) for mute LED */
+static int cx_auto_vmaster_mute_led(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct conexant_spec *spec = codec->spec;
+
+ snd_hda_codec_write(codec, spec->mute_led_eapd, 0,
+ AC_VERB_SET_EAPD_BTLENABLE,
+ brightness ? 0x02 : 0x00);
+ return 0;
+}
+
+static void cxt_init_gpio_led(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask;
+
+ if (mask) {
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
+ mask);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
+ mask);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+ spec->gpio_led);
+ }
+}
+
+static int cx_auto_init(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ snd_hda_gen_init(codec);
+ if (!spec->dynamic_eapd)
+ cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true);
+
+ cxt_init_gpio_led(codec);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
+
+ return 0;
+}
+
+static void cx_auto_reboot_notify(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ /* Turn the problematic codec into D3 to avoid spurious noises
+ from the internal speaker during (and after) reboot */
+ cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, false);
+ snd_hda_gen_reboot_notify(codec);
+}
+
+static void cx_auto_free(struct hda_codec *codec)
+{
+ cx_auto_reboot_notify(codec);
+ snd_hda_gen_free(codec);
+}
+
+static const struct hda_codec_ops cx_auto_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = cx_auto_init,
+ .reboot_notify = cx_auto_reboot_notify,
+ .free = cx_auto_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .check_power_status = snd_hda_gen_check_power_status,
+#endif
+};
+
+/*
+ * pin fix-up
+ */
+enum {
+ CXT_PINCFG_LENOVO_X200,
+ CXT_PINCFG_LENOVO_TP410,
+ CXT_PINCFG_LEMOTE_A1004,
+ CXT_PINCFG_LEMOTE_A1205,
+ CXT_PINCFG_COMPAQ_CQ60,
+ CXT_FIXUP_STEREO_DMIC,
+ CXT_PINCFG_LENOVO_NOTEBOOK,
+ CXT_FIXUP_INC_MIC_BOOST,
+ CXT_FIXUP_HEADPHONE_MIC_PIN,
+ CXT_FIXUP_HEADPHONE_MIC,
+ CXT_FIXUP_GPIO1,
+ CXT_FIXUP_ASPIRE_DMIC,
+ CXT_FIXUP_THINKPAD_ACPI,
+ CXT_FIXUP_OLPC_XO,
+ CXT_FIXUP_CAP_MIX_AMP,
+ CXT_FIXUP_TOSHIBA_P105,
+ CXT_FIXUP_HP_530,
+ CXT_FIXUP_CAP_MIX_AMP_5047,
+ CXT_FIXUP_MUTE_LED_EAPD,
+ CXT_FIXUP_HP_DOCK,
+ CXT_FIXUP_HP_SPECTRE,
+ CXT_FIXUP_HP_GATE_MIC,
+ CXT_FIXUP_MUTE_LED_GPIO,
+ CXT_FIXUP_HP_ZBOOK_MUTE_LED,
+ CXT_FIXUP_HEADSET_MIC,
+ CXT_FIXUP_HP_MIC_NO_PRESENCE,
+};
+
+/* for hda_fixup_thinkpad_acpi() */
+#include "thinkpad_helper.c"
+
+static void cxt_fixup_stereo_dmic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+ spec->gen.inv_dmic_split = 1;
+}
+
+static void cxt5066_increase_mic_boost(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ snd_hda_override_amp_caps(codec, 0x17, HDA_OUTPUT,
+ (0x3 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x4 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+static void cxt_update_headset_mode(struct hda_codec *codec)
+{
+ /* The verbs used in this function were tested on a Conexant CX20751/2 codec. */
+ int i;
+ bool mic_mode = false;
+ struct conexant_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+
+ hda_nid_t mux_pin = spec->gen.imux_pins[spec->gen.cur_mux[0]];
+
+ for (i = 0; i < cfg->num_inputs; i++)
+ if (cfg->inputs[i].pin == mux_pin) {
+ mic_mode = !!cfg->inputs[i].is_headphone_mic;
+ break;
+ }
+
+ if (mic_mode) {
+ snd_hda_codec_write_cache(codec, 0x1c, 0, 0x410, 0x7c); /* enable merged mode for analog int-mic */
+ spec->gen.hp_jack_present = false;
+ } else {
+ snd_hda_codec_write_cache(codec, 0x1c, 0, 0x410, 0x54); /* disable merged mode for analog int-mic */
+ spec->gen.hp_jack_present = snd_hda_jack_detect(codec, spec->gen.autocfg.hp_pins[0]);
+ }
+
+ snd_hda_gen_update_outputs(codec);
+}
+
+static void cxt_update_headset_mode_hook(struct hda_codec *codec,
+ struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ cxt_update_headset_mode(codec);
+}
+
+static void cxt_fixup_headphone_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->parse_flags |= HDA_PINCFG_HEADPHONE_MIC;
+ snd_hdac_regmap_add_vendor_verb(&codec->core, 0x410);
+ break;
+ case HDA_FIXUP_ACT_PROBE:
+ WARN_ON(spec->gen.cap_sync_hook);
+ spec->gen.cap_sync_hook = cxt_update_headset_mode_hook;
+ spec->gen.automute_hook = cxt_update_headset_mode;
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ cxt_update_headset_mode(codec);
+ break;
+ }
+}
+
+static void cxt_fixup_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ break;
+ }
+}
+
+/* OPLC XO 1.5 fixup */
+
+/* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors)
+ * through the microphone jack.
+ * When the user enables this through a mixer switch, both internal and
+ * external microphones are disabled. Gain is fixed at 0dB. In this mode,
+ * we also allow the bias to be configured through a separate mixer
+ * control. */
+
+#define update_mic_pin(codec, nid, val) \
+ snd_hda_codec_write_cache(codec, nid, 0, \
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val)
+
+static const struct hda_input_mux olpc_xo_dc_bias = {
+ .num_items = 3,
+ .items = {
+ { "Off", PIN_IN },
+ { "50%", PIN_VREF50 },
+ { "80%", PIN_VREF80 },
+ },
+};
+
+static void olpc_xo_update_mic_boost(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ int ch, val;
+
+ for (ch = 0; ch < 2; ch++) {
+ val = AC_AMP_SET_OUTPUT |
+ (ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT);
+ if (!spec->dc_enable)
+ val |= snd_hda_codec_amp_read(codec, 0x17, ch, HDA_OUTPUT, 0);
+ snd_hda_codec_write(codec, 0x17, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, val);
+ }
+}
+
+static void olpc_xo_update_mic_pins(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ int cur_input, val;
+ struct nid_path *path;
+
+ cur_input = spec->gen.input_paths[0][spec->gen.cur_mux[0]];
+
+ /* Set up mic pins for port-B, C and F dynamically as the recording
+ * LED is turned on/off by these pin controls
+ */
+ if (!spec->dc_enable) {
+ /* disable DC bias path and pin for port F */
+ update_mic_pin(codec, 0x1e, 0);
+ snd_hda_activate_path(codec, spec->dc_mode_path, false, false);
+
+ /* update port B (ext mic) and C (int mic) */
+ /* OLPC defers mic widget control until when capture is
+ * started because the microphone LED comes on as soon as
+ * these settings are put in place. if we did this before
+ * recording, it would give the false indication that
+ * recording is happening when it is not.
+ */
+ update_mic_pin(codec, 0x1a, spec->recording ?
+ snd_hda_codec_get_pin_target(codec, 0x1a) : 0);
+ update_mic_pin(codec, 0x1b, spec->recording ?
+ snd_hda_codec_get_pin_target(codec, 0x1b) : 0);
+ /* enable normal mic path */
+ path = snd_hda_get_path_from_idx(codec, cur_input);
+ if (path)
+ snd_hda_activate_path(codec, path, true, false);
+ } else {
+ /* disable normal mic path */
+ path = snd_hda_get_path_from_idx(codec, cur_input);
+ if (path)
+ snd_hda_activate_path(codec, path, false, false);
+
+ /* Even though port F is the DC input, the bias is controlled
+ * on port B. We also leave that port as an active input (but
+ * unselected) in DC mode just in case that is necessary to
+ * make the bias setting take effect.
+ */
+ if (spec->recording)
+ val = olpc_xo_dc_bias.items[spec->dc_input_bias].index;
+ else
+ val = 0;
+ update_mic_pin(codec, 0x1a, val);
+ update_mic_pin(codec, 0x1b, 0);
+ /* enable DC bias path and pin */
+ update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0);
+ snd_hda_activate_path(codec, spec->dc_mode_path, true, false);
+ }
+}
+
+/* mic_autoswitch hook */
+static void olpc_xo_automic(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ /* in DC mode, we don't handle automic */
+ if (!spec->dc_enable)
+ snd_hda_gen_mic_autoswitch(codec, jack);
+ olpc_xo_update_mic_pins(codec);
+ if (spec->dc_enable)
+ olpc_xo_update_mic_boost(codec);
+}
+
+/* pcm_capture hook */
+static void olpc_xo_capture_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ /* toggle spec->recording flag and update mic pins accordingly
+ * for turning on/off LED
+ */
+ switch (action) {
+ case HDA_GEN_PCM_ACT_PREPARE:
+ spec->recording = 1;
+ olpc_xo_update_mic_pins(codec);
+ break;
+ case HDA_GEN_PCM_ACT_CLEANUP:
+ spec->recording = 0;
+ olpc_xo_update_mic_pins(codec);
+ break;
+ }
+}
+
+static int olpc_xo_dc_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct conexant_spec *spec = codec->spec;
+ ucontrol->value.integer.value[0] = spec->dc_enable;
+ return 0;
+}
+
+static int olpc_xo_dc_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct conexant_spec *spec = codec->spec;
+ int dc_enable = !!ucontrol->value.integer.value[0];
+
+ if (dc_enable == spec->dc_enable)
+ return 0;
+
+ spec->dc_enable = dc_enable;
+ olpc_xo_update_mic_pins(codec);
+ olpc_xo_update_mic_boost(codec);
+ return 1;
+}
+
+static int olpc_xo_dc_bias_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct conexant_spec *spec = codec->spec;
+ ucontrol->value.enumerated.item[0] = spec->dc_input_bias;
+ return 0;
+}
+
+static int olpc_xo_dc_bias_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_hda_input_mux_info(&olpc_xo_dc_bias, uinfo);
+}
+
+static int olpc_xo_dc_bias_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct conexant_spec *spec = codec->spec;
+ const struct hda_input_mux *imux = &olpc_xo_dc_bias;
+ unsigned int idx;
+
+ idx = ucontrol->value.enumerated.item[0];
+ if (idx >= imux->num_items)
+ idx = imux->num_items - 1;
+ if (spec->dc_input_bias == idx)
+ return 0;
+
+ spec->dc_input_bias = idx;
+ if (spec->dc_enable)
+ olpc_xo_update_mic_pins(codec);
+ return 1;
+}
+
+static const struct snd_kcontrol_new olpc_xo_mixers[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DC Mode Enable Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = olpc_xo_dc_mode_get,
+ .put = olpc_xo_dc_mode_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DC Input Bias Enum",
+ .info = olpc_xo_dc_bias_enum_info,
+ .get = olpc_xo_dc_bias_enum_get,
+ .put = olpc_xo_dc_bias_enum_put,
+ },
+ {}
+};
+
+/* overriding mic boost put callback; update mic boost volume only when
+ * DC mode is disabled
+ */
+static int olpc_xo_mic_boost_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct conexant_spec *spec = codec->spec;
+ int ret = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol);
+ if (ret > 0 && spec->dc_enable)
+ olpc_xo_update_mic_boost(codec);
+ return ret;
+}
+
+static void cxt_fixup_olpc_xo(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+ struct snd_kcontrol_new *kctl;
+ int i;
+
+ if (action != HDA_FIXUP_ACT_PROBE)
+ return;
+
+ spec->gen.mic_autoswitch_hook = olpc_xo_automic;
+ spec->gen.pcm_capture_hook = olpc_xo_capture_hook;
+ spec->dc_mode_path = snd_hda_add_new_path(codec, 0x1e, 0x14, 0);
+
+ snd_hda_add_new_ctls(codec, olpc_xo_mixers);
+
+ /* OLPC's microphone port is DC coupled for use with external sensors,
+ * therefore we use a 50% mic bias in order to center the input signal
+ * with the DC input range of the codec.
+ */
+ snd_hda_codec_set_pin_target(codec, 0x1a, PIN_VREF50);
+
+ /* override mic boost control */
+ snd_array_for_each(&spec->gen.kctls, i, kctl) {
+ if (!strcmp(kctl->name, "Mic Boost Volume")) {
+ kctl->put = olpc_xo_mic_boost_put;
+ break;
+ }
+ }
+}
+
+static void cxt_fixup_mute_led_eapd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mute_led_eapd = 0x1b;
+ spec->dynamic_eapd = 1;
+ snd_hda_gen_add_mute_led_cdev(codec, cx_auto_vmaster_mute_led);
+ }
+}
+
+/*
+ * Fix max input level on mixer widget to 0dB
+ * (originally it has 0x2b steps with 0dB offset 0x14)
+ */
+static void cxt_fixup_cap_mix_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ snd_hda_override_amp_caps(codec, 0x17, HDA_INPUT,
+ (0x14 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x14 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (1 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+/*
+ * Fix max input level on mixer widget to 0dB
+ * (originally it has 0x1e steps with 0 dB offset 0x17)
+ */
+static void cxt_fixup_cap_mix_amp_5047(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ snd_hda_override_amp_caps(codec, 0x10, HDA_INPUT,
+ (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (1 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ /* the mic pin (0x19) doesn't give an unsolicited event;
+ * probe the mic pin together with the headphone pin (0x16)
+ */
+ if (action == HDA_FIXUP_ACT_PROBE)
+ snd_hda_jack_set_gating_jack(codec, 0x19, 0x16);
+}
+
+/* update LED status via GPIO */
+static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask,
+ bool led_on)
+{
+ struct conexant_spec *spec = codec->spec;
+ unsigned int oldval = spec->gpio_led;
+
+ if (spec->mute_led_polarity)
+ led_on = !led_on;
+
+ if (led_on)
+ spec->gpio_led |= mask;
+ else
+ spec->gpio_led &= ~mask;
+ codec_dbg(codec, "mask:%d enabled:%d gpio_led:%d\n",
+ mask, led_on, spec->gpio_led);
+ if (spec->gpio_led != oldval)
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+ spec->gpio_led);
+}
+
+/* turn on/off mute LED via GPIO per vmaster hook */
+static int cxt_gpio_mute_update(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct conexant_spec *spec = codec->spec;
+
+ cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, brightness);
+ return 0;
+}
+
+/* turn on/off mic-mute LED via GPIO per capture hook */
+static int cxt_gpio_micmute_update(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct conexant_spec *spec = codec->spec;
+
+ cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, brightness);
+ return 0;
+}
+
+static void cxt_setup_mute_led(struct hda_codec *codec,
+ unsigned int mute, unsigned int mic_mute)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ spec->gpio_led = 0;
+ spec->mute_led_polarity = 0;
+ if (mute) {
+ snd_hda_gen_add_mute_led_cdev(codec, cxt_gpio_mute_update);
+ spec->gpio_mute_led_mask = mute;
+ }
+ if (mic_mute) {
+ snd_hda_gen_add_micmute_led_cdev(codec, cxt_gpio_micmute_update);
+ spec->gpio_mic_led_mask = mic_mute;
+ }
+}
+
+static void cxt_fixup_mute_led_gpio(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ cxt_setup_mute_led(codec, 0x01, 0x02);
+}
+
+static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ cxt_setup_mute_led(codec, 0x10, 0x20);
+}
+
+/* ThinkPad X200 & co with cxt5051 */
+static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = {
+ { 0x16, 0x042140ff }, /* HP (seq# overridden) */
+ { 0x17, 0x21a11000 }, /* dock-mic */
+ { 0x19, 0x2121103f }, /* dock-HP */
+ { 0x1c, 0x21440100 }, /* dock SPDIF out */
+ {}
+};
+
+/* ThinkPad 410/420/510/520, X201 & co with cxt5066 */
+static const struct hda_pintbl cxt_pincfg_lenovo_tp410[] = {
+ { 0x19, 0x042110ff }, /* HP (seq# overridden) */
+ { 0x1a, 0x21a190f0 }, /* dock-mic */
+ { 0x1c, 0x212140ff }, /* dock-HP */
+ {}
+};
+
+/* Lemote A1004/A1205 with cxt5066 */
+static const struct hda_pintbl cxt_pincfg_lemote[] = {
+ { 0x1a, 0x90a10020 }, /* Internal mic */
+ { 0x1b, 0x03a11020 }, /* External mic */
+ { 0x1d, 0x400101f0 }, /* Not used */
+ { 0x1e, 0x40a701f0 }, /* Not used */
+ { 0x20, 0x404501f0 }, /* Not used */
+ { 0x22, 0x404401f0 }, /* Not used */
+ { 0x23, 0x40a701f0 }, /* Not used */
+ {}
+};
+
+static const struct hda_fixup cxt_fixups[] = {
+ [CXT_PINCFG_LENOVO_X200] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cxt_pincfg_lenovo_x200,
+ },
+ [CXT_PINCFG_LENOVO_TP410] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cxt_pincfg_lenovo_tp410,
+ .chained = true,
+ .chain_id = CXT_FIXUP_THINKPAD_ACPI,
+ },
+ [CXT_PINCFG_LEMOTE_A1004] = {
+ .type = HDA_FIXUP_PINS,
+ .chained = true,
+ .chain_id = CXT_FIXUP_INC_MIC_BOOST,
+ .v.pins = cxt_pincfg_lemote,
+ },
+ [CXT_PINCFG_LEMOTE_A1205] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cxt_pincfg_lemote,
+ },
+ [CXT_PINCFG_COMPAQ_CQ60] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* 0x17 was falsely set up as a mic, it should 0x1d */
+ { 0x17, 0x400001f0 },
+ { 0x1d, 0x97a70120 },
+ { }
+ }
+ },
+ [CXT_FIXUP_STEREO_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_stereo_dmic,
+ },
+ [CXT_PINCFG_LENOVO_NOTEBOOK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x05d71030 },
+ { }
+ },
+ .chain_id = CXT_FIXUP_STEREO_DMIC,
+ },
+ [CXT_FIXUP_INC_MIC_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt5066_increase_mic_boost,
+ },
+ [CXT_FIXUP_HEADPHONE_MIC_PIN] = {
+ .type = HDA_FIXUP_PINS,
+ .chained = true,
+ .chain_id = CXT_FIXUP_HEADPHONE_MIC,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x03a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ }
+ },
+ [CXT_FIXUP_HEADPHONE_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_headphone_mic,
+ },
+ [CXT_FIXUP_GPIO1] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x01, AC_VERB_SET_GPIO_MASK, 0x01 },
+ { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01 },
+ { 0x01, AC_VERB_SET_GPIO_DATA, 0x01 },
+ { }
+ },
+ },
+ [CXT_FIXUP_ASPIRE_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_stereo_dmic,
+ .chained = true,
+ .chain_id = CXT_FIXUP_GPIO1,
+ },
+ [CXT_FIXUP_THINKPAD_ACPI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = hda_fixup_thinkpad_acpi,
+ },
+ [CXT_FIXUP_OLPC_XO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_olpc_xo,
+ },
+ [CXT_FIXUP_CAP_MIX_AMP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_cap_mix_amp,
+ },
+ [CXT_FIXUP_TOSHIBA_P105] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x10, 0x961701f0 }, /* speaker/hp */
+ { 0x12, 0x02a1901e }, /* ext mic */
+ { 0x14, 0x95a70110 }, /* int mic */
+ {}
+ },
+ },
+ [CXT_FIXUP_HP_530] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x90a60160 }, /* int mic */
+ {}
+ },
+ .chained = true,
+ .chain_id = CXT_FIXUP_CAP_MIX_AMP,
+ },
+ [CXT_FIXUP_CAP_MIX_AMP_5047] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_cap_mix_amp_5047,
+ },
+ [CXT_FIXUP_MUTE_LED_EAPD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_mute_led_eapd,
+ },
+ [CXT_FIXUP_HP_DOCK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x21011020 }, /* line-out */
+ { 0x18, 0x2181103f }, /* line-in */
+ { }
+ },
+ .chained = true,
+ .chain_id = CXT_FIXUP_MUTE_LED_GPIO,
+ },
+ [CXT_FIXUP_HP_SPECTRE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* enable NID 0x1d for the speaker on top */
+ { 0x1d, 0x91170111 },
+ { }
+ }
+ },
+ [CXT_FIXUP_HP_GATE_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_hp_gate_mic_jack,
+ },
+ [CXT_FIXUP_MUTE_LED_GPIO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_mute_led_gpio,
+ },
+ [CXT_FIXUP_HP_ZBOOK_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_hp_zbook_mute_led,
+ },
+ [CXT_FIXUP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_headset_mic,
+ },
+ [CXT_FIXUP_HP_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x02a1113c },
+ { }
+ },
+ .chained = true,
+ .chain_id = CXT_FIXUP_HEADSET_MIC,
+ },
+};
+
+static const struct snd_pci_quirk cxt5045_fixups[] = {
+ SND_PCI_QUIRK(0x103c, 0x30d5, "HP 530", CXT_FIXUP_HP_530),
+ SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P105", CXT_FIXUP_TOSHIBA_P105),
+ /* HP, Packard Bell, Fujitsu-Siemens & Lenovo laptops have
+ * really bad sound over 0dB on NID 0x17.
+ */
+ SND_PCI_QUIRK_VENDOR(0x103c, "HP", CXT_FIXUP_CAP_MIX_AMP),
+ SND_PCI_QUIRK_VENDOR(0x1631, "Packard Bell", CXT_FIXUP_CAP_MIX_AMP),
+ SND_PCI_QUIRK_VENDOR(0x1734, "Fujitsu", CXT_FIXUP_CAP_MIX_AMP),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo", CXT_FIXUP_CAP_MIX_AMP),
+ {}
+};
+
+static const struct hda_model_fixup cxt5045_fixup_models[] = {
+ { .id = CXT_FIXUP_CAP_MIX_AMP, .name = "cap-mix-amp" },
+ { .id = CXT_FIXUP_TOSHIBA_P105, .name = "toshiba-p105" },
+ { .id = CXT_FIXUP_HP_530, .name = "hp-530" },
+ {}
+};
+
+static const struct snd_pci_quirk cxt5047_fixups[] = {
+ /* HP laptops have really bad sound over 0 dB on NID 0x10.
+ */
+ SND_PCI_QUIRK_VENDOR(0x103c, "HP", CXT_FIXUP_CAP_MIX_AMP_5047),
+ {}
+};
+
+static const struct hda_model_fixup cxt5047_fixup_models[] = {
+ { .id = CXT_FIXUP_CAP_MIX_AMP_5047, .name = "cap-mix-amp" },
+ {}
+};
+
+static const struct snd_pci_quirk cxt5051_fixups[] = {
+ SND_PCI_QUIRK(0x103c, 0x360b, "Compaq CQ60", CXT_PINCFG_COMPAQ_CQ60),
+ SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT_PINCFG_LENOVO_X200),
+ {}
+};
+
+static const struct hda_model_fixup cxt5051_fixup_models[] = {
+ { .id = CXT_PINCFG_LENOVO_X200, .name = "lenovo-x200" },
+ {}
+};
+
+static const struct snd_pci_quirk cxt5066_fixups[] = {
+ SND_PCI_QUIRK(0x1025, 0x0543, "Acer Aspire One 522", CXT_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC),
+ SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC),
+ SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE),
+ SND_PCI_QUIRK(0x103c, 0x822e, "HP ProBook 440 G4", CXT_FIXUP_MUTE_LED_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x828c, "HP EliteBook 840 G4", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x8299, "HP 800 G3 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x83d3, "HP ProBook 640 G4", CXT_FIXUP_HP_DOCK),
+ SND_PCI_QUIRK(0x103c, 0x8402, "HP ProBook 645 G4", CXT_FIXUP_MUTE_LED_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x8427, "HP ZBook Studio G5", CXT_FIXUP_HP_ZBOOK_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x844f, "HP ZBook Studio G5", CXT_FIXUP_HP_ZBOOK_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x8455, "HP Z2 G4", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x8456, "HP Z2 G4 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x8457, "HP Z2 G4 mini", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x8458, "HP Z2 G4 mini premium", CXT_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN),
+ SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO),
+ SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo T410", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x215f, "Lenovo T510", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21ce, "Lenovo T420", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21cf, "Lenovo T520", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21d2, "Lenovo T420s", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21da, "Lenovo X220", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21db, "Lenovo X220-tablet", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo IdeaPad Z560", CXT_FIXUP_MUTE_LED_EAPD),
+ SND_PCI_QUIRK(0x17aa, 0x3905, "Lenovo G50-30", CXT_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x17aa, 0x390b, "Lenovo G50-80", CXT_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC),
+ /* NOTE: we'd need to extend the quirk for 17aa:3977 as the same
+ * PCI SSID is used on multiple Lenovo models
+ */
+ SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", CXT_FIXUP_THINKPAD_ACPI),
+ SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004),
+ SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205),
+ {}
+};
+
+static const struct hda_model_fixup cxt5066_fixup_models[] = {
+ { .id = CXT_FIXUP_STEREO_DMIC, .name = "stereo-dmic" },
+ { .id = CXT_FIXUP_GPIO1, .name = "gpio1" },
+ { .id = CXT_FIXUP_HEADPHONE_MIC_PIN, .name = "headphone-mic-pin" },
+ { .id = CXT_PINCFG_LENOVO_TP410, .name = "tp410" },
+ { .id = CXT_FIXUP_THINKPAD_ACPI, .name = "thinkpad" },
+ { .id = CXT_PINCFG_LEMOTE_A1004, .name = "lemote-a1004" },
+ { .id = CXT_PINCFG_LEMOTE_A1205, .name = "lemote-a1205" },
+ { .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" },
+ { .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" },
+ { .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" },
+ { .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" },
+ { .id = CXT_FIXUP_HP_ZBOOK_MUTE_LED, .name = "hp-zbook-mute-led" },
+ { .id = CXT_FIXUP_HP_MIC_NO_PRESENCE, .name = "hp-mic-fix" },
+ { .id = CXT_PINCFG_LENOVO_NOTEBOOK, .name = "lenovo-20149" },
+ {}
+};
+
+/* add "fake" mute amp-caps to DACs on cx5051 so that mixer mute switches
+ * can be created (bko#42825)
+ */
+static void add_cx5051_fake_mutes(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ static const hda_nid_t out_nids[] = {
+ 0x10, 0x11, 0
+ };
+ const hda_nid_t *p;
+
+ for (p = out_nids; *p; p++)
+ snd_hda_override_amp_caps(codec, *p, HDA_OUTPUT,
+ AC_AMPCAP_MIN_MUTE |
+ query_amp_caps(codec, *p, HDA_OUTPUT));
+ spec->gen.dac_min_mute = true;
+}
+
+static int patch_conexant_auto(struct hda_codec *codec)
+{
+ struct conexant_spec *spec;
+ int err;
+
+ codec_info(codec, "%s: BIOS auto-probing.\n", codec->core.chip_name);
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ snd_hda_gen_spec_init(&spec->gen);
+ codec->spec = spec;
+ codec->patch_ops = cx_auto_patch_ops;
+
+ cx_auto_parse_eapd(codec);
+ spec->gen.own_eapd_ctl = 1;
+
+ switch (codec->core.vendor_id) {
+ case 0x14f15045:
+ codec->single_adc_amp = 1;
+ spec->gen.mixer_nid = 0x17;
+ spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO;
+ snd_hda_pick_fixup(codec, cxt5045_fixup_models,
+ cxt5045_fixups, cxt_fixups);
+ break;
+ case 0x14f15047:
+ codec->pin_amp_workaround = 1;
+ spec->gen.mixer_nid = 0x19;
+ spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO;
+ snd_hda_pick_fixup(codec, cxt5047_fixup_models,
+ cxt5047_fixups, cxt_fixups);
+ break;
+ case 0x14f15051:
+ add_cx5051_fake_mutes(codec);
+ codec->pin_amp_workaround = 1;
+ snd_hda_pick_fixup(codec, cxt5051_fixup_models,
+ cxt5051_fixups, cxt_fixups);
+ break;
+ case 0x14f15098:
+ codec->pin_amp_workaround = 1;
+ spec->gen.mixer_nid = 0x22;
+ spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO;
+ snd_hda_pick_fixup(codec, cxt5066_fixup_models,
+ cxt5066_fixups, cxt_fixups);
+ break;
+ case 0x14f150f2:
+ codec->power_save_node = 1;
+ fallthrough;
+ default:
+ codec->pin_amp_workaround = 1;
+ snd_hda_pick_fixup(codec, cxt5066_fixup_models,
+ cxt5066_fixups, cxt_fixups);
+ break;
+ }
+
+ if (!spec->gen.vmaster_mute.hook && spec->dynamic_eapd)
+ spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL,
+ spec->parse_flags);
+ if (err < 0)
+ goto error;
+
+ err = cx_auto_parse_beep(codec);
+ if (err < 0)
+ goto error;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
+ if (err < 0)
+ goto error;
+
+ /* Some laptops with Conexant chips show stalls in S3 resume,
+ * which falls into the single-cmd mode.
+ * Better to make reset, then.
+ */
+ if (!codec->bus->core.sync_write) {
+ codec_info(codec,
+ "Enable sync_write for stable communication\n");
+ codec->bus->core.sync_write = 1;
+ codec->bus->allow_bus_reset = 1;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ cx_auto_free(codec);
+ return err;
+}
+
+/*
+ */
+
+static const struct hda_device_id snd_hda_id_conexant[] = {
+ HDA_CODEC_ENTRY(0x14f11f86, "CX8070", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f12008, "CX8200", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f120d0, "CX11970", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f120d1, "SN6180", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15045, "CX20549 (Venice)", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15047, "CX20551 (Waikiki)", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15051, "CX20561 (Hermosa)", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15066, "CX20582 (Pebble)", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15067, "CX20583 (Pebble HSF)", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15068, "CX20584", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15069, "CX20585", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f1506c, "CX20588", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f1506e, "CX20590", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15097, "CX20631", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15098, "CX20632", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150a1, "CX20641", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150a2, "CX20642", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150ab, "CX20651", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150ac, "CX20652", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150b8, "CX20664", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150b9, "CX20665", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150f1, "CX21722", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150f2, "CX20722", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150f3, "CX21724", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f150f4, "CX20724", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f1510f, "CX20751/2", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15110, "CX20751/2", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15111, "CX20753/4", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15113, "CX20755", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15114, "CX20756", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f15115, "CX20757", patch_conexant_auto),
+ HDA_CODEC_ENTRY(0x14f151d7, "CX20952", patch_conexant_auto),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_conexant);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Conexant HD-audio codec");
+
+static struct hda_codec_driver conexant_driver = {
+ .id = snd_hda_id_conexant,
+};
+
+module_hda_codec_driver(conexant_driver);
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
new file mode 100644
index 000000000..0d1c6c4c1
--- /dev/null
+++ b/sound/pci/hda/patch_hdmi.c
@@ -0,0 +1,4458 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * patch_hdmi.c - routines for HDMI/DisplayPort codecs
+ *
+ * Copyright(c) 2008-2010 Intel Corporation. All rights reserved.
+ * Copyright (c) 2006 ATI Technologies Inc.
+ * Copyright (c) 2008 NVIDIA Corp. All rights reserved.
+ * Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ * Authors:
+ * Wu Fengguang <wfg@linux.intel.com>
+ *
+ * Maintained by:
+ * Wu Fengguang <wfg@linux.intel.com>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/asoundef.h>
+#include <sound/tlv.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
+#include <sound/hda_chmap.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_jack.h"
+#include "hda_controller.h"
+
+static bool static_hdmi_pcm;
+module_param(static_hdmi_pcm, bool, 0644);
+MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info");
+
+static bool enable_acomp = true;
+module_param(enable_acomp, bool, 0444);
+MODULE_PARM_DESC(enable_acomp, "Enable audio component binding (default=yes)");
+
+static bool enable_silent_stream =
+IS_ENABLED(CONFIG_SND_HDA_INTEL_HDMI_SILENT_STREAM);
+module_param(enable_silent_stream, bool, 0644);
+MODULE_PARM_DESC(enable_silent_stream, "Enable Silent Stream for HDMI devices");
+
+struct hdmi_spec_per_cvt {
+ hda_nid_t cvt_nid;
+ int assigned;
+ unsigned int channels_min;
+ unsigned int channels_max;
+ u32 rates;
+ u64 formats;
+ unsigned int maxbps;
+};
+
+/* max. connections to a widget */
+#define HDA_MAX_CONNECTIONS 32
+
+struct hdmi_spec_per_pin {
+ hda_nid_t pin_nid;
+ int dev_id;
+ /* pin idx, different device entries on the same pin use the same idx */
+ int pin_nid_idx;
+ int num_mux_nids;
+ hda_nid_t mux_nids[HDA_MAX_CONNECTIONS];
+ int mux_idx;
+ hda_nid_t cvt_nid;
+
+ struct hda_codec *codec;
+ struct hdmi_eld sink_eld;
+ struct mutex lock;
+ struct delayed_work work;
+ struct hdmi_pcm *pcm; /* pointer to spec->pcm_rec[n] dynamically*/
+ int pcm_idx; /* which pcm is attached. -1 means no pcm is attached */
+ int repoll_count;
+ bool setup; /* the stream has been set up by prepare callback */
+ bool silent_stream;
+ int channels; /* current number of channels */
+ bool non_pcm;
+ bool chmap_set; /* channel-map override by ALSA API? */
+ unsigned char chmap[8]; /* ALSA API channel-map */
+#ifdef CONFIG_SND_PROC_FS
+ struct snd_info_entry *proc_entry;
+#endif
+};
+
+/* operations used by generic code that can be overridden by patches */
+struct hdmi_ops {
+ int (*pin_get_eld)(struct hda_codec *codec, hda_nid_t pin_nid,
+ int dev_id, unsigned char *buf, int *eld_size);
+
+ void (*pin_setup_infoframe)(struct hda_codec *codec, hda_nid_t pin_nid,
+ int dev_id,
+ int ca, int active_channels, int conn_type);
+
+ /* enable/disable HBR (HD passthrough) */
+ int (*pin_hbr_setup)(struct hda_codec *codec, hda_nid_t pin_nid,
+ int dev_id, bool hbr);
+
+ int (*setup_stream)(struct hda_codec *codec, hda_nid_t cvt_nid,
+ hda_nid_t pin_nid, int dev_id, u32 stream_tag,
+ int format);
+
+ void (*pin_cvt_fixup)(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin,
+ hda_nid_t cvt_nid);
+};
+
+struct hdmi_pcm {
+ struct hda_pcm *pcm;
+ struct snd_jack *jack;
+ struct snd_kcontrol *eld_ctl;
+};
+
+struct hdmi_spec {
+ struct hda_codec *codec;
+ int num_cvts;
+ struct snd_array cvts; /* struct hdmi_spec_per_cvt */
+ hda_nid_t cvt_nids[4]; /* only for haswell fix */
+
+ /*
+ * num_pins is the number of virtual pins
+ * for example, there are 3 pins, and each pin
+ * has 4 device entries, then the num_pins is 12
+ */
+ int num_pins;
+ /*
+ * num_nids is the number of real pins
+ * In the above example, num_nids is 3
+ */
+ int num_nids;
+ /*
+ * dev_num is the number of device entries
+ * on each pin.
+ * In the above example, dev_num is 4
+ */
+ int dev_num;
+ struct snd_array pins; /* struct hdmi_spec_per_pin */
+ struct hdmi_pcm pcm_rec[16];
+ struct mutex pcm_lock;
+ struct mutex bind_lock; /* for audio component binding */
+ /* pcm_bitmap means which pcms have been assigned to pins*/
+ unsigned long pcm_bitmap;
+ int pcm_used; /* counter of pcm_rec[] */
+ /* bitmap shows whether the pcm is opened in user space
+ * bit 0 means the first playback PCM (PCM3);
+ * bit 1 means the second playback PCM, and so on.
+ */
+ unsigned long pcm_in_use;
+
+ struct hdmi_eld temp_eld;
+ struct hdmi_ops ops;
+
+ bool dyn_pin_out;
+ bool dyn_pcm_assign;
+ bool dyn_pcm_no_legacy;
+ bool nv_dp_workaround; /* workaround DP audio infoframe for Nvidia */
+
+ bool intel_hsw_fixup; /* apply Intel platform-specific fixups */
+ /*
+ * Non-generic VIA/NVIDIA specific
+ */
+ struct hda_multi_out multiout;
+ struct hda_pcm_stream pcm_playback;
+
+ bool use_acomp_notifier; /* use eld_notify callback for hotplug */
+ bool acomp_registered; /* audio component registered in this driver */
+ bool force_connect; /* force connectivity */
+ struct drm_audio_component_audio_ops drm_audio_ops;
+ int (*port2pin)(struct hda_codec *, int); /* reverse port/pin mapping */
+
+ struct hdac_chmap chmap;
+ hda_nid_t vendor_nid;
+ const int *port_map;
+ int port_num;
+ bool send_silent_stream; /* Flag to enable silent stream feature */
+};
+
+#ifdef CONFIG_SND_HDA_COMPONENT
+static inline bool codec_has_acomp(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ return spec->use_acomp_notifier;
+}
+#else
+#define codec_has_acomp(codec) false
+#endif
+
+struct hdmi_audio_infoframe {
+ u8 type; /* 0x84 */
+ u8 ver; /* 0x01 */
+ u8 len; /* 0x0a */
+
+ u8 checksum;
+
+ u8 CC02_CT47; /* CC in bits 0:2, CT in 4:7 */
+ u8 SS01_SF24;
+ u8 CXT04;
+ u8 CA;
+ u8 LFEPBL01_LSV36_DM_INH7;
+};
+
+struct dp_audio_infoframe {
+ u8 type; /* 0x84 */
+ u8 len; /* 0x1b */
+ u8 ver; /* 0x11 << 2 */
+
+ u8 CC02_CT47; /* match with HDMI infoframe from this on */
+ u8 SS01_SF24;
+ u8 CXT04;
+ u8 CA;
+ u8 LFEPBL01_LSV36_DM_INH7;
+};
+
+union audio_infoframe {
+ struct hdmi_audio_infoframe hdmi;
+ struct dp_audio_infoframe dp;
+ u8 bytes[0];
+};
+
+/*
+ * HDMI routines
+ */
+
+#define get_pin(spec, idx) \
+ ((struct hdmi_spec_per_pin *)snd_array_elem(&spec->pins, idx))
+#define get_cvt(spec, idx) \
+ ((struct hdmi_spec_per_cvt *)snd_array_elem(&spec->cvts, idx))
+/* obtain hdmi_pcm object assigned to idx */
+#define get_hdmi_pcm(spec, idx) (&(spec)->pcm_rec[idx])
+/* obtain hda_pcm object assigned to idx */
+#define get_pcm_rec(spec, idx) (get_hdmi_pcm(spec, idx)->pcm)
+
+static int pin_id_to_pin_index(struct hda_codec *codec,
+ hda_nid_t pin_nid, int dev_id)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+ struct hdmi_spec_per_pin *per_pin;
+
+ /*
+ * (dev_id == -1) means it is NON-MST pin
+ * return the first virtual pin on this port
+ */
+ if (dev_id == -1)
+ dev_id = 0;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ per_pin = get_pin(spec, pin_idx);
+ if ((per_pin->pin_nid == pin_nid) &&
+ (per_pin->dev_id == dev_id))
+ return pin_idx;
+ }
+
+ codec_warn(codec, "HDMI: pin nid %d not registered\n", pin_nid);
+ return -EINVAL;
+}
+
+static int hinfo_to_pcm_index(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pcm_idx;
+
+ for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++)
+ if (get_pcm_rec(spec, pcm_idx)->stream == hinfo)
+ return pcm_idx;
+
+ codec_warn(codec, "HDMI: hinfo %p not tied to a PCM\n", hinfo);
+ return -EINVAL;
+}
+
+static int hinfo_to_pin_index(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin;
+ int pin_idx;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ per_pin = get_pin(spec, pin_idx);
+ if (per_pin->pcm &&
+ per_pin->pcm->pcm->stream == hinfo)
+ return pin_idx;
+ }
+
+ codec_dbg(codec, "HDMI: hinfo %p (pcm %d) not registered\n", hinfo,
+ hinfo_to_pcm_index(codec, hinfo));
+ return -EINVAL;
+}
+
+static struct hdmi_spec_per_pin *pcm_idx_to_pin(struct hdmi_spec *spec,
+ int pcm_idx)
+{
+ int i;
+ struct hdmi_spec_per_pin *per_pin;
+
+ for (i = 0; i < spec->num_pins; i++) {
+ per_pin = get_pin(spec, i);
+ if (per_pin->pcm_idx == pcm_idx)
+ return per_pin;
+ }
+ return NULL;
+}
+
+static int cvt_nid_to_cvt_index(struct hda_codec *codec, hda_nid_t cvt_nid)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int cvt_idx;
+
+ for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++)
+ if (get_cvt(spec, cvt_idx)->cvt_nid == cvt_nid)
+ return cvt_idx;
+
+ codec_warn(codec, "HDMI: cvt nid %d not registered\n", cvt_nid);
+ return -EINVAL;
+}
+
+static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin;
+ struct hdmi_eld *eld;
+ int pcm_idx;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+
+ pcm_idx = kcontrol->private_value;
+ mutex_lock(&spec->pcm_lock);
+ per_pin = pcm_idx_to_pin(spec, pcm_idx);
+ if (!per_pin) {
+ /* no pin is bound to the pcm */
+ uinfo->count = 0;
+ goto unlock;
+ }
+ eld = &per_pin->sink_eld;
+ uinfo->count = eld->eld_valid ? eld->eld_size : 0;
+
+ unlock:
+ mutex_unlock(&spec->pcm_lock);
+ return 0;
+}
+
+static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin;
+ struct hdmi_eld *eld;
+ int pcm_idx;
+ int err = 0;
+
+ pcm_idx = kcontrol->private_value;
+ mutex_lock(&spec->pcm_lock);
+ per_pin = pcm_idx_to_pin(spec, pcm_idx);
+ if (!per_pin) {
+ /* no pin is bound to the pcm */
+ memset(ucontrol->value.bytes.data, 0,
+ ARRAY_SIZE(ucontrol->value.bytes.data));
+ goto unlock;
+ }
+
+ eld = &per_pin->sink_eld;
+ if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data) ||
+ eld->eld_size > ELD_MAX_SIZE) {
+ snd_BUG();
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ memset(ucontrol->value.bytes.data, 0,
+ ARRAY_SIZE(ucontrol->value.bytes.data));
+ if (eld->eld_valid)
+ memcpy(ucontrol->value.bytes.data, eld->eld_buffer,
+ eld->eld_size);
+
+ unlock:
+ mutex_unlock(&spec->pcm_lock);
+ return err;
+}
+
+static const struct snd_kcontrol_new eld_bytes_ctl = {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE |
+ SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "ELD",
+ .info = hdmi_eld_ctl_info,
+ .get = hdmi_eld_ctl_get,
+};
+
+static int hdmi_create_eld_ctl(struct hda_codec *codec, int pcm_idx,
+ int device)
+{
+ struct snd_kcontrol *kctl;
+ struct hdmi_spec *spec = codec->spec;
+ int err;
+
+ kctl = snd_ctl_new1(&eld_bytes_ctl, codec);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->private_value = pcm_idx;
+ kctl->id.device = device;
+
+ /* no pin nid is associated with the kctl now
+ * tbd: associate pin nid to eld ctl later
+ */
+ err = snd_hda_ctl_add(codec, 0, kctl);
+ if (err < 0)
+ return err;
+
+ get_hdmi_pcm(spec, pcm_idx)->eld_ctl = kctl;
+ return 0;
+}
+
+#ifdef BE_PARANOID
+static void hdmi_get_dip_index(struct hda_codec *codec, hda_nid_t pin_nid,
+ int *packet_index, int *byte_index)
+{
+ int val;
+
+ val = snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_HDMI_DIP_INDEX, 0);
+
+ *packet_index = val >> 5;
+ *byte_index = val & 0x1f;
+}
+#endif
+
+static void hdmi_set_dip_index(struct hda_codec *codec, hda_nid_t pin_nid,
+ int packet_index, int byte_index)
+{
+ int val;
+
+ val = (packet_index << 5) | (byte_index & 0x1f);
+
+ snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_INDEX, val);
+}
+
+static void hdmi_write_dip_byte(struct hda_codec *codec, hda_nid_t pin_nid,
+ unsigned char val)
+{
+ snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_DATA, val);
+}
+
+static void hdmi_init_pin(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_out;
+
+ /* Unmute */
+ if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP)
+ snd_hda_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
+
+ if (spec->dyn_pin_out)
+ /* Disable pin out until stream is active */
+ pin_out = 0;
+ else
+ /* Enable pin out: some machines with GM965 gets broken output
+ * when the pin is disabled or changed while using with HDMI
+ */
+ pin_out = PIN_OUT;
+
+ snd_hda_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, pin_out);
+}
+
+/*
+ * ELD proc files
+ */
+
+#ifdef CONFIG_SND_PROC_FS
+static void print_eld_info(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct hdmi_spec_per_pin *per_pin = entry->private_data;
+
+ mutex_lock(&per_pin->lock);
+ snd_hdmi_print_eld_info(&per_pin->sink_eld, buffer);
+ mutex_unlock(&per_pin->lock);
+}
+
+static void write_eld_info(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct hdmi_spec_per_pin *per_pin = entry->private_data;
+
+ mutex_lock(&per_pin->lock);
+ snd_hdmi_write_eld_info(&per_pin->sink_eld, buffer);
+ mutex_unlock(&per_pin->lock);
+}
+
+static int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index)
+{
+ char name[32];
+ struct hda_codec *codec = per_pin->codec;
+ struct snd_info_entry *entry;
+ int err;
+
+ snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, index);
+ err = snd_card_proc_new(codec->card, name, &entry);
+ if (err < 0)
+ return err;
+
+ snd_info_set_text_ops(entry, per_pin, print_eld_info);
+ entry->c.text.write = write_eld_info;
+ entry->mode |= 0200;
+ per_pin->proc_entry = entry;
+
+ return 0;
+}
+
+static void eld_proc_free(struct hdmi_spec_per_pin *per_pin)
+{
+ if (!per_pin->codec->bus->shutdown) {
+ snd_info_free_entry(per_pin->proc_entry);
+ per_pin->proc_entry = NULL;
+ }
+}
+#else
+static inline int eld_proc_new(struct hdmi_spec_per_pin *per_pin,
+ int index)
+{
+ return 0;
+}
+static inline void eld_proc_free(struct hdmi_spec_per_pin *per_pin)
+{
+}
+#endif
+
+/*
+ * Audio InfoFrame routines
+ */
+
+/*
+ * Enable Audio InfoFrame Transmission
+ */
+static void hdmi_start_infoframe_trans(struct hda_codec *codec,
+ hda_nid_t pin_nid)
+{
+ hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+ snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT,
+ AC_DIPXMIT_BEST);
+}
+
+/*
+ * Disable Audio InfoFrame Transmission
+ */
+static void hdmi_stop_infoframe_trans(struct hda_codec *codec,
+ hda_nid_t pin_nid)
+{
+ hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+ snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT,
+ AC_DIPXMIT_DISABLE);
+}
+
+static void hdmi_debug_dip_size(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ int i;
+ int size;
+
+ size = snd_hdmi_get_eld_size(codec, pin_nid);
+ codec_dbg(codec, "HDMI: ELD buf size is %d\n", size);
+
+ for (i = 0; i < 8; i++) {
+ size = snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_HDMI_DIP_SIZE, i);
+ codec_dbg(codec, "HDMI: DIP GP[%d] buf size is %d\n", i, size);
+ }
+#endif
+}
+
+static void hdmi_clear_dip_buffers(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+#ifdef BE_PARANOID
+ int i, j;
+ int size;
+ int pi, bi;
+ for (i = 0; i < 8; i++) {
+ size = snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_HDMI_DIP_SIZE, i);
+ if (size == 0)
+ continue;
+
+ hdmi_set_dip_index(codec, pin_nid, i, 0x0);
+ for (j = 1; j < 1000; j++) {
+ hdmi_write_dip_byte(codec, pin_nid, 0x0);
+ hdmi_get_dip_index(codec, pin_nid, &pi, &bi);
+ if (pi != i)
+ codec_dbg(codec, "dip index %d: %d != %d\n",
+ bi, pi, i);
+ if (bi == 0) /* byte index wrapped around */
+ break;
+ }
+ codec_dbg(codec,
+ "HDMI: DIP GP[%d] buf reported size=%d, written=%d\n",
+ i, size, j);
+ }
+#endif
+}
+
+static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *hdmi_ai)
+{
+ u8 *bytes = (u8 *)hdmi_ai;
+ u8 sum = 0;
+ int i;
+
+ hdmi_ai->checksum = 0;
+
+ for (i = 0; i < sizeof(*hdmi_ai); i++)
+ sum += bytes[i];
+
+ hdmi_ai->checksum = -sum;
+}
+
+static void hdmi_fill_audio_infoframe(struct hda_codec *codec,
+ hda_nid_t pin_nid,
+ u8 *dip, int size)
+{
+ int i;
+
+ hdmi_debug_dip_size(codec, pin_nid);
+ hdmi_clear_dip_buffers(codec, pin_nid); /* be paranoid */
+
+ hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+ for (i = 0; i < size; i++)
+ hdmi_write_dip_byte(codec, pin_nid, dip[i]);
+}
+
+static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
+ u8 *dip, int size)
+{
+ u8 val;
+ int i;
+
+ if (snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_XMIT, 0)
+ != AC_DIPXMIT_BEST)
+ return false;
+
+ hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+ for (i = 0; i < size; i++) {
+ val = snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_HDMI_DIP_DATA, 0);
+ if (val != dip[i])
+ return false;
+ }
+
+ return true;
+}
+
+static int hdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, unsigned char *buf, int *eld_size)
+{
+ snd_hda_set_dev_select(codec, nid, dev_id);
+
+ return snd_hdmi_get_eld(codec, nid, buf, eld_size);
+}
+
+static void hdmi_pin_setup_infoframe(struct hda_codec *codec,
+ hda_nid_t pin_nid, int dev_id,
+ int ca, int active_channels,
+ int conn_type)
+{
+ struct hdmi_spec *spec = codec->spec;
+ union audio_infoframe ai;
+
+ memset(&ai, 0, sizeof(ai));
+ if ((conn_type == 0) || /* HDMI */
+ /* Nvidia DisplayPort: Nvidia HW expects same layout as HDMI */
+ (conn_type == 1 && spec->nv_dp_workaround)) {
+ struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi;
+
+ if (conn_type == 0) { /* HDMI */
+ hdmi_ai->type = 0x84;
+ hdmi_ai->ver = 0x01;
+ hdmi_ai->len = 0x0a;
+ } else {/* Nvidia DP */
+ hdmi_ai->type = 0x84;
+ hdmi_ai->ver = 0x1b;
+ hdmi_ai->len = 0x11 << 2;
+ }
+ hdmi_ai->CC02_CT47 = active_channels - 1;
+ hdmi_ai->CA = ca;
+ hdmi_checksum_audio_infoframe(hdmi_ai);
+ } else if (conn_type == 1) { /* DisplayPort */
+ struct dp_audio_infoframe *dp_ai = &ai.dp;
+
+ dp_ai->type = 0x84;
+ dp_ai->len = 0x1b;
+ dp_ai->ver = 0x11 << 2;
+ dp_ai->CC02_CT47 = active_channels - 1;
+ dp_ai->CA = ca;
+ } else {
+ codec_dbg(codec, "HDMI: unknown connection type at pin %d\n",
+ pin_nid);
+ return;
+ }
+
+ snd_hda_set_dev_select(codec, pin_nid, dev_id);
+
+ /*
+ * sizeof(ai) is used instead of sizeof(*hdmi_ai) or
+ * sizeof(*dp_ai) to avoid partial match/update problems when
+ * the user switches between HDMI/DP monitors.
+ */
+ if (!hdmi_infoframe_uptodate(codec, pin_nid, ai.bytes,
+ sizeof(ai))) {
+ codec_dbg(codec,
+ "hdmi_pin_setup_infoframe: pin=%d channels=%d ca=0x%02x\n",
+ pin_nid,
+ active_channels, ca);
+ hdmi_stop_infoframe_trans(codec, pin_nid);
+ hdmi_fill_audio_infoframe(codec, pin_nid,
+ ai.bytes, sizeof(ai));
+ hdmi_start_infoframe_trans(codec, pin_nid);
+ }
+}
+
+static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin,
+ bool non_pcm)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdac_chmap *chmap = &spec->chmap;
+ hda_nid_t pin_nid = per_pin->pin_nid;
+ int dev_id = per_pin->dev_id;
+ int channels = per_pin->channels;
+ int active_channels;
+ struct hdmi_eld *eld;
+ int ca;
+
+ if (!channels)
+ return;
+
+ snd_hda_set_dev_select(codec, pin_nid, dev_id);
+
+ /* some HW (e.g. HSW+) needs reprogramming the amp at each time */
+ if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP)
+ snd_hda_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE,
+ AMP_OUT_UNMUTE);
+
+ eld = &per_pin->sink_eld;
+
+ ca = snd_hdac_channel_allocation(&codec->core,
+ eld->info.spk_alloc, channels,
+ per_pin->chmap_set, non_pcm, per_pin->chmap);
+
+ active_channels = snd_hdac_get_active_channels(ca);
+
+ chmap->ops.set_channel_count(&codec->core, per_pin->cvt_nid,
+ active_channels);
+
+ /*
+ * always configure channel mapping, it may have been changed by the
+ * user in the meantime
+ */
+ snd_hdac_setup_channel_mapping(&spec->chmap,
+ pin_nid, non_pcm, ca, channels,
+ per_pin->chmap, per_pin->chmap_set);
+
+ spec->ops.pin_setup_infoframe(codec, pin_nid, dev_id,
+ ca, active_channels, eld->info.conn_type);
+
+ per_pin->non_pcm = non_pcm;
+}
+
+/*
+ * Unsolicited events
+ */
+
+static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll);
+
+static void check_presence_and_report(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx = pin_id_to_pin_index(codec, nid, dev_id);
+
+ if (pin_idx < 0)
+ return;
+ mutex_lock(&spec->pcm_lock);
+ hdmi_present_sense(get_pin(spec, pin_idx), 1);
+ mutex_unlock(&spec->pcm_lock);
+}
+
+static void jack_callback(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ /* stop polling when notification is enabled */
+ if (codec_has_acomp(codec))
+ return;
+
+ check_presence_and_report(codec, jack->nid, jack->dev_id);
+}
+
+static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res,
+ struct hda_jack_tbl *jack)
+{
+ jack->jack_dirty = 1;
+
+ codec_dbg(codec,
+ "HDMI hot plug event: Codec=%d Pin=%d Device=%d Inactive=%d Presence_Detect=%d ELD_Valid=%d\n",
+ codec->addr, jack->nid, jack->dev_id, !!(res & AC_UNSOL_RES_IA),
+ !!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV));
+
+ check_presence_and_report(codec, jack->nid, jack->dev_id);
+}
+
+static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
+{
+ int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
+ int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT;
+ int cp_state = !!(res & AC_UNSOL_RES_CP_STATE);
+ int cp_ready = !!(res & AC_UNSOL_RES_CP_READY);
+
+ codec_info(codec,
+ "HDMI CP event: CODEC=%d TAG=%d SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n",
+ codec->addr,
+ tag,
+ subtag,
+ cp_state,
+ cp_ready);
+
+ /* TODO */
+ if (cp_state) {
+ ;
+ }
+ if (cp_ready) {
+ ;
+ }
+}
+
+
+static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+ int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
+ int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT;
+ struct hda_jack_tbl *jack;
+
+ if (codec_has_acomp(codec))
+ return;
+
+ if (codec->dp_mst) {
+ int dev_entry =
+ (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
+
+ jack = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
+ } else {
+ jack = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
+ }
+
+ if (!jack) {
+ codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag);
+ return;
+ }
+
+ if (subtag == 0)
+ hdmi_intrinsic_event(codec, res, jack);
+ else
+ hdmi_non_intrinsic_event(codec, res);
+}
+
+static void haswell_verify_D0(struct hda_codec *codec,
+ hda_nid_t cvt_nid, hda_nid_t nid)
+{
+ int pwr;
+
+ /* For Haswell, the converter 1/2 may keep in D3 state after bootup,
+ * thus pins could only choose converter 0 for use. Make sure the
+ * converters are in correct power state */
+ if (!snd_hda_check_power_state(codec, cvt_nid, AC_PWRST_D0))
+ snd_hda_codec_write(codec, cvt_nid, 0, AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+
+ if (!snd_hda_check_power_state(codec, nid, AC_PWRST_D0)) {
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE,
+ AC_PWRST_D0);
+ msleep(40);
+ pwr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
+ pwr = (pwr & AC_PWRST_ACTUAL) >> AC_PWRST_ACTUAL_SHIFT;
+ codec_dbg(codec, "Haswell HDMI audio: Power for pin 0x%x is now D%d\n", nid, pwr);
+ }
+}
+
+/*
+ * Callbacks
+ */
+
+/* HBR should be Non-PCM, 8 channels */
+#define is_hbr_format(format) \
+ ((format & AC_FMT_TYPE_NON_PCM) && (format & AC_FMT_CHAN_MASK) == 7)
+
+static int hdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
+ int dev_id, bool hbr)
+{
+ int pinctl, new_pinctl;
+
+ if (snd_hda_query_pin_caps(codec, pin_nid) & AC_PINCAP_HBR) {
+ snd_hda_set_dev_select(codec, pin_nid, dev_id);
+ pinctl = snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+
+ if (pinctl < 0)
+ return hbr ? -EINVAL : 0;
+
+ new_pinctl = pinctl & ~AC_PINCTL_EPT;
+ if (hbr)
+ new_pinctl |= AC_PINCTL_EPT_HBR;
+ else
+ new_pinctl |= AC_PINCTL_EPT_NATIVE;
+
+ codec_dbg(codec,
+ "hdmi_pin_hbr_setup: NID=0x%x, %spinctl=0x%x\n",
+ pin_nid,
+ pinctl == new_pinctl ? "" : "new-",
+ new_pinctl);
+
+ if (pinctl != new_pinctl)
+ snd_hda_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ new_pinctl);
+ } else if (hbr)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
+ hda_nid_t pin_nid, int dev_id,
+ u32 stream_tag, int format)
+{
+ struct hdmi_spec *spec = codec->spec;
+ unsigned int param;
+ int err;
+
+ err = spec->ops.pin_hbr_setup(codec, pin_nid, dev_id,
+ is_hbr_format(format));
+
+ if (err) {
+ codec_dbg(codec, "hdmi_setup_stream: HBR is not supported\n");
+ return err;
+ }
+
+ if (spec->intel_hsw_fixup) {
+
+ /*
+ * on recent platforms IEC Coding Type is required for HBR
+ * support, read current Digital Converter settings and set
+ * ICT bitfield if needed.
+ */
+ param = snd_hda_codec_read(codec, cvt_nid, 0,
+ AC_VERB_GET_DIGI_CONVERT_1, 0);
+
+ param = (param >> 16) & ~(AC_DIG3_ICT);
+
+ /* on recent platforms ICT mode is required for HBR support */
+ if (is_hbr_format(format))
+ param |= 0x1;
+
+ snd_hda_codec_write(codec, cvt_nid, 0,
+ AC_VERB_SET_DIGI_CONVERT_3, param);
+ }
+
+ snd_hda_codec_setup_stream(codec, cvt_nid, stream_tag, 0, format);
+ return 0;
+}
+
+/* Try to find an available converter
+ * If pin_idx is less then zero, just try to find an available converter.
+ * Otherwise, try to find an available converter and get the cvt mux index
+ * of the pin.
+ */
+static int hdmi_choose_cvt(struct hda_codec *codec,
+ int pin_idx, int *cvt_id)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin;
+ struct hdmi_spec_per_cvt *per_cvt = NULL;
+ int cvt_idx, mux_idx = 0;
+
+ /* pin_idx < 0 means no pin will be bound to the converter */
+ if (pin_idx < 0)
+ per_pin = NULL;
+ else
+ per_pin = get_pin(spec, pin_idx);
+
+ if (per_pin && per_pin->silent_stream) {
+ cvt_idx = cvt_nid_to_cvt_index(codec, per_pin->cvt_nid);
+ if (cvt_id)
+ *cvt_id = cvt_idx;
+ return 0;
+ }
+
+ /* Dynamically assign converter to stream */
+ for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+ per_cvt = get_cvt(spec, cvt_idx);
+
+ /* Must not already be assigned */
+ if (per_cvt->assigned)
+ continue;
+ if (per_pin == NULL)
+ break;
+ /* Must be in pin's mux's list of converters */
+ for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++)
+ if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid)
+ break;
+ /* Not in mux list */
+ if (mux_idx == per_pin->num_mux_nids)
+ continue;
+ break;
+ }
+
+ /* No free converters */
+ if (cvt_idx == spec->num_cvts)
+ return -EBUSY;
+
+ if (per_pin != NULL)
+ per_pin->mux_idx = mux_idx;
+
+ if (cvt_id)
+ *cvt_id = cvt_idx;
+
+ return 0;
+}
+
+/* Assure the pin select the right convetor */
+static void intel_verify_pin_cvt_connect(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ hda_nid_t pin_nid = per_pin->pin_nid;
+ int mux_idx, curr;
+
+ mux_idx = per_pin->mux_idx;
+ curr = snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_CONNECT_SEL, 0);
+ if (curr != mux_idx)
+ snd_hda_codec_write_cache(codec, pin_nid, 0,
+ AC_VERB_SET_CONNECT_SEL,
+ mux_idx);
+}
+
+/* get the mux index for the converter of the pins
+ * converter's mux index is the same for all pins on Intel platform
+ */
+static int intel_cvt_id_to_mux_idx(struct hdmi_spec *spec,
+ hda_nid_t cvt_nid)
+{
+ int i;
+
+ for (i = 0; i < spec->num_cvts; i++)
+ if (spec->cvt_nids[i] == cvt_nid)
+ return i;
+ return -EINVAL;
+}
+
+/* Intel HDMI workaround to fix audio routing issue:
+ * For some Intel display codecs, pins share the same connection list.
+ * So a conveter can be selected by multiple pins and playback on any of these
+ * pins will generate sound on the external display, because audio flows from
+ * the same converter to the display pipeline. Also muting one pin may make
+ * other pins have no sound output.
+ * So this function assures that an assigned converter for a pin is not selected
+ * by any other pins.
+ */
+static void intel_not_share_assigned_cvt(struct hda_codec *codec,
+ hda_nid_t pin_nid,
+ int dev_id, int mux_idx)
+{
+ struct hdmi_spec *spec = codec->spec;
+ hda_nid_t nid;
+ int cvt_idx, curr;
+ struct hdmi_spec_per_cvt *per_cvt;
+ struct hdmi_spec_per_pin *per_pin;
+ int pin_idx;
+
+ /* configure the pins connections */
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ int dev_id_saved;
+ int dev_num;
+
+ per_pin = get_pin(spec, pin_idx);
+ /*
+ * pin not connected to monitor
+ * no need to operate on it
+ */
+ if (!per_pin->pcm)
+ continue;
+
+ if ((per_pin->pin_nid == pin_nid) &&
+ (per_pin->dev_id == dev_id))
+ continue;
+
+ /*
+ * if per_pin->dev_id >= dev_num,
+ * snd_hda_get_dev_select() will fail,
+ * and the following operation is unpredictable.
+ * So skip this situation.
+ */
+ dev_num = snd_hda_get_num_devices(codec, per_pin->pin_nid) + 1;
+ if (per_pin->dev_id >= dev_num)
+ continue;
+
+ nid = per_pin->pin_nid;
+
+ /*
+ * Calling this function should not impact
+ * on the device entry selection
+ * So let's save the dev id for each pin,
+ * and restore it when return
+ */
+ dev_id_saved = snd_hda_get_dev_select(codec, nid);
+ snd_hda_set_dev_select(codec, nid, per_pin->dev_id);
+ curr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_CONNECT_SEL, 0);
+ if (curr != mux_idx) {
+ snd_hda_set_dev_select(codec, nid, dev_id_saved);
+ continue;
+ }
+
+
+ /* choose an unassigned converter. The conveters in the
+ * connection list are in the same order as in the codec.
+ */
+ for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+ per_cvt = get_cvt(spec, cvt_idx);
+ if (!per_cvt->assigned) {
+ codec_dbg(codec,
+ "choose cvt %d for pin nid %d\n",
+ cvt_idx, nid);
+ snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_CONNECT_SEL,
+ cvt_idx);
+ break;
+ }
+ }
+ snd_hda_set_dev_select(codec, nid, dev_id_saved);
+ }
+}
+
+/* A wrapper of intel_not_share_asigned_cvt() */
+static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec,
+ hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid)
+{
+ int mux_idx;
+ struct hdmi_spec *spec = codec->spec;
+
+ /* On Intel platform, the mapping of converter nid to
+ * mux index of the pins are always the same.
+ * The pin nid may be 0, this means all pins will not
+ * share the converter.
+ */
+ mux_idx = intel_cvt_id_to_mux_idx(spec, cvt_nid);
+ if (mux_idx >= 0)
+ intel_not_share_assigned_cvt(codec, pin_nid, dev_id, mux_idx);
+}
+
+/* skeleton caller of pin_cvt_fixup ops */
+static void pin_cvt_fixup(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin,
+ hda_nid_t cvt_nid)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ if (spec->ops.pin_cvt_fixup)
+ spec->ops.pin_cvt_fixup(codec, per_pin, cvt_nid);
+}
+
+/* called in hdmi_pcm_open when no pin is assigned to the PCM
+ * in dyn_pcm_assign mode.
+ */
+static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int cvt_idx, pcm_idx;
+ struct hdmi_spec_per_cvt *per_cvt = NULL;
+ int err;
+
+ pcm_idx = hinfo_to_pcm_index(codec, hinfo);
+ if (pcm_idx < 0)
+ return -EINVAL;
+
+ err = hdmi_choose_cvt(codec, -1, &cvt_idx);
+ if (err)
+ return err;
+
+ per_cvt = get_cvt(spec, cvt_idx);
+ per_cvt->assigned = 1;
+ hinfo->nid = per_cvt->cvt_nid;
+
+ pin_cvt_fixup(codec, NULL, per_cvt->cvt_nid);
+
+ set_bit(pcm_idx, &spec->pcm_in_use);
+ /* todo: setup spdif ctls assign */
+
+ /* Initially set the converter's capabilities */
+ hinfo->channels_min = per_cvt->channels_min;
+ hinfo->channels_max = per_cvt->channels_max;
+ hinfo->rates = per_cvt->rates;
+ hinfo->formats = per_cvt->formats;
+ hinfo->maxbps = per_cvt->maxbps;
+
+ /* Store the updated parameters */
+ runtime->hw.channels_min = hinfo->channels_min;
+ runtime->hw.channels_max = hinfo->channels_max;
+ runtime->hw.formats = hinfo->formats;
+ runtime->hw.rates = hinfo->rates;
+
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+ return 0;
+}
+
+/*
+ * HDA PCM callbacks
+ */
+static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int pin_idx, cvt_idx, pcm_idx;
+ struct hdmi_spec_per_pin *per_pin;
+ struct hdmi_eld *eld;
+ struct hdmi_spec_per_cvt *per_cvt = NULL;
+ int err;
+
+ /* Validate hinfo */
+ pcm_idx = hinfo_to_pcm_index(codec, hinfo);
+ if (pcm_idx < 0)
+ return -EINVAL;
+
+ mutex_lock(&spec->pcm_lock);
+ pin_idx = hinfo_to_pin_index(codec, hinfo);
+ if (!spec->dyn_pcm_assign) {
+ if (snd_BUG_ON(pin_idx < 0)) {
+ err = -EINVAL;
+ goto unlock;
+ }
+ } else {
+ /* no pin is assigned to the PCM
+ * PA need pcm open successfully when probe
+ */
+ if (pin_idx < 0) {
+ err = hdmi_pcm_open_no_pin(hinfo, codec, substream);
+ goto unlock;
+ }
+ }
+
+ err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx);
+ if (err < 0)
+ goto unlock;
+
+ per_cvt = get_cvt(spec, cvt_idx);
+ /* Claim converter */
+ per_cvt->assigned = 1;
+
+ set_bit(pcm_idx, &spec->pcm_in_use);
+ per_pin = get_pin(spec, pin_idx);
+ per_pin->cvt_nid = per_cvt->cvt_nid;
+ per_pin->silent_stream = false;
+ hinfo->nid = per_cvt->cvt_nid;
+
+ /* flip stripe flag for the assigned stream if supported */
+ if (get_wcaps(codec, per_cvt->cvt_nid) & AC_WCAP_STRIPE)
+ azx_stream(get_azx_dev(substream))->stripe = 1;
+
+ snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id);
+ snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
+ AC_VERB_SET_CONNECT_SEL,
+ per_pin->mux_idx);
+
+ /* configure unused pins to choose other converters */
+ pin_cvt_fixup(codec, per_pin, 0);
+
+ snd_hda_spdif_ctls_assign(codec, pcm_idx, per_cvt->cvt_nid);
+
+ /* Initially set the converter's capabilities */
+ hinfo->channels_min = per_cvt->channels_min;
+ hinfo->channels_max = per_cvt->channels_max;
+ hinfo->rates = per_cvt->rates;
+ hinfo->formats = per_cvt->formats;
+ hinfo->maxbps = per_cvt->maxbps;
+
+ eld = &per_pin->sink_eld;
+ /* Restrict capabilities by ELD if this isn't disabled */
+ if (!static_hdmi_pcm && eld->eld_valid) {
+ snd_hdmi_eld_update_pcm_info(&eld->info, hinfo);
+ if (hinfo->channels_min > hinfo->channels_max ||
+ !hinfo->rates || !hinfo->formats) {
+ per_cvt->assigned = 0;
+ hinfo->nid = 0;
+ snd_hda_spdif_ctls_unassign(codec, pcm_idx);
+ err = -ENODEV;
+ goto unlock;
+ }
+ }
+
+ /* Store the updated parameters */
+ runtime->hw.channels_min = hinfo->channels_min;
+ runtime->hw.channels_max = hinfo->channels_max;
+ runtime->hw.formats = hinfo->formats;
+ runtime->hw.rates = hinfo->rates;
+
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+ unlock:
+ mutex_unlock(&spec->pcm_lock);
+ return err;
+}
+
+/*
+ * HDA/HDMI auto parsing
+ */
+static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ hda_nid_t pin_nid = per_pin->pin_nid;
+ int dev_id = per_pin->dev_id;
+ int conns;
+
+ if (!(get_wcaps(codec, pin_nid) & AC_WCAP_CONN_LIST)) {
+ codec_warn(codec,
+ "HDMI: pin %d wcaps %#x does not support connection list\n",
+ pin_nid, get_wcaps(codec, pin_nid));
+ return -EINVAL;
+ }
+
+ snd_hda_set_dev_select(codec, pin_nid, dev_id);
+
+ if (spec->intel_hsw_fixup) {
+ conns = spec->num_cvts;
+ memcpy(per_pin->mux_nids, spec->cvt_nids,
+ sizeof(hda_nid_t) * conns);
+ } else {
+ conns = snd_hda_get_raw_connections(codec, pin_nid,
+ per_pin->mux_nids,
+ HDA_MAX_CONNECTIONS);
+ }
+
+ /* all the device entries on the same pin have the same conn list */
+ per_pin->num_mux_nids = conns;
+
+ return 0;
+}
+
+static int hdmi_find_pcm_slot(struct hdmi_spec *spec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ int i;
+
+ /* on the new machines, try to assign the pcm slot dynamically,
+ * not use the preferred fixed map (legacy way) anymore.
+ */
+ if (spec->dyn_pcm_no_legacy)
+ goto last_try;
+
+ /*
+ * generic_hdmi_build_pcms() may allocate extra PCMs on some
+ * platforms (with maximum of 'num_nids + dev_num - 1')
+ *
+ * The per_pin of pin_nid_idx=n and dev_id=m prefers to get pcm-n
+ * if m==0. This guarantees that dynamic pcm assignments are compatible
+ * with the legacy static per_pin-pcm assignment that existed in the
+ * days before DP-MST.
+ *
+ * Intel DP-MST prefers this legacy behavior for compatibility, too.
+ *
+ * per_pin of m!=0 prefers to get pcm=(num_nids + (m - 1)).
+ */
+
+ if (per_pin->dev_id == 0 || spec->intel_hsw_fixup) {
+ if (!test_bit(per_pin->pin_nid_idx, &spec->pcm_bitmap))
+ return per_pin->pin_nid_idx;
+ } else {
+ i = spec->num_nids + (per_pin->dev_id - 1);
+ if (i < spec->pcm_used && !(test_bit(i, &spec->pcm_bitmap)))
+ return i;
+ }
+
+ /* have a second try; check the area over num_nids */
+ for (i = spec->num_nids; i < spec->pcm_used; i++) {
+ if (!test_bit(i, &spec->pcm_bitmap))
+ return i;
+ }
+
+ last_try:
+ /* the last try; check the empty slots in pins */
+ for (i = 0; i < spec->pcm_used; i++) {
+ if (!test_bit(i, &spec->pcm_bitmap))
+ return i;
+ }
+ return -EBUSY;
+}
+
+static void hdmi_attach_hda_pcm(struct hdmi_spec *spec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ int idx;
+
+ /* pcm already be attached to the pin */
+ if (per_pin->pcm)
+ return;
+ idx = hdmi_find_pcm_slot(spec, per_pin);
+ if (idx == -EBUSY)
+ return;
+ per_pin->pcm_idx = idx;
+ per_pin->pcm = get_hdmi_pcm(spec, idx);
+ set_bit(idx, &spec->pcm_bitmap);
+}
+
+static void hdmi_detach_hda_pcm(struct hdmi_spec *spec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ int idx;
+
+ /* pcm already be detached from the pin */
+ if (!per_pin->pcm)
+ return;
+ idx = per_pin->pcm_idx;
+ per_pin->pcm_idx = -1;
+ per_pin->pcm = NULL;
+ if (idx >= 0 && idx < spec->pcm_used)
+ clear_bit(idx, &spec->pcm_bitmap);
+}
+
+static int hdmi_get_pin_cvt_mux(struct hdmi_spec *spec,
+ struct hdmi_spec_per_pin *per_pin, hda_nid_t cvt_nid)
+{
+ int mux_idx;
+
+ for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++)
+ if (per_pin->mux_nids[mux_idx] == cvt_nid)
+ break;
+ return mux_idx;
+}
+
+static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid);
+
+static void hdmi_pcm_setup_pin(struct hdmi_spec *spec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ struct hda_codec *codec = per_pin->codec;
+ struct hda_pcm *pcm;
+ struct hda_pcm_stream *hinfo;
+ struct snd_pcm_substream *substream;
+ int mux_idx;
+ bool non_pcm;
+
+ if (per_pin->pcm_idx >= 0 && per_pin->pcm_idx < spec->pcm_used)
+ pcm = get_pcm_rec(spec, per_pin->pcm_idx);
+ else
+ return;
+ if (!pcm->pcm)
+ return;
+ if (!test_bit(per_pin->pcm_idx, &spec->pcm_in_use))
+ return;
+
+ /* hdmi audio only uses playback and one substream */
+ hinfo = pcm->stream;
+ substream = pcm->pcm->streams[0].substream;
+
+ per_pin->cvt_nid = hinfo->nid;
+
+ mux_idx = hdmi_get_pin_cvt_mux(spec, per_pin, hinfo->nid);
+ if (mux_idx < per_pin->num_mux_nids) {
+ snd_hda_set_dev_select(codec, per_pin->pin_nid,
+ per_pin->dev_id);
+ snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
+ AC_VERB_SET_CONNECT_SEL,
+ mux_idx);
+ }
+ snd_hda_spdif_ctls_assign(codec, per_pin->pcm_idx, hinfo->nid);
+
+ non_pcm = check_non_pcm_per_cvt(codec, hinfo->nid);
+ if (substream->runtime)
+ per_pin->channels = substream->runtime->channels;
+ per_pin->setup = true;
+ per_pin->mux_idx = mux_idx;
+
+ hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
+}
+
+static void hdmi_pcm_reset_pin(struct hdmi_spec *spec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ if (per_pin->pcm_idx >= 0 && per_pin->pcm_idx < spec->pcm_used)
+ snd_hda_spdif_ctls_unassign(per_pin->codec, per_pin->pcm_idx);
+
+ per_pin->chmap_set = false;
+ memset(per_pin->chmap, 0, sizeof(per_pin->chmap));
+
+ per_pin->setup = false;
+ per_pin->channels = 0;
+}
+
+static struct snd_jack *pin_idx_to_pcm_jack(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ if (per_pin->pcm_idx >= 0)
+ return spec->pcm_rec[per_pin->pcm_idx].jack;
+ else
+ return NULL;
+}
+
+/* update per_pin ELD from the given new ELD;
+ * setup info frame and notification accordingly
+ * also notify ELD kctl and report jack status changes
+ */
+static void update_eld(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin,
+ struct hdmi_eld *eld,
+ int repoll)
+{
+ struct hdmi_eld *pin_eld = &per_pin->sink_eld;
+ struct hdmi_spec *spec = codec->spec;
+ struct snd_jack *pcm_jack;
+ bool old_eld_valid = pin_eld->eld_valid;
+ bool eld_changed;
+ int pcm_idx;
+
+ if (eld->eld_valid) {
+ if (eld->eld_size <= 0 ||
+ snd_hdmi_parse_eld(codec, &eld->info, eld->eld_buffer,
+ eld->eld_size) < 0) {
+ eld->eld_valid = false;
+ if (repoll) {
+ schedule_delayed_work(&per_pin->work,
+ msecs_to_jiffies(300));
+ return;
+ }
+ }
+ }
+
+ if (!eld->eld_valid || eld->eld_size <= 0) {
+ eld->eld_valid = false;
+ eld->eld_size = 0;
+ }
+
+ /* for monitor disconnection, save pcm_idx firstly */
+ pcm_idx = per_pin->pcm_idx;
+
+ /*
+ * pcm_idx >=0 before update_eld() means it is in monitor
+ * disconnected event. Jack must be fetched before update_eld().
+ */
+ pcm_jack = pin_idx_to_pcm_jack(codec, per_pin);
+
+ if (spec->dyn_pcm_assign) {
+ if (eld->eld_valid) {
+ hdmi_attach_hda_pcm(spec, per_pin);
+ hdmi_pcm_setup_pin(spec, per_pin);
+ } else {
+ hdmi_pcm_reset_pin(spec, per_pin);
+ hdmi_detach_hda_pcm(spec, per_pin);
+ }
+ }
+ /* if pcm_idx == -1, it means this is in monitor connection event
+ * we can get the correct pcm_idx now.
+ */
+ if (pcm_idx == -1)
+ pcm_idx = per_pin->pcm_idx;
+ if (!pcm_jack)
+ pcm_jack = pin_idx_to_pcm_jack(codec, per_pin);
+
+ if (eld->eld_valid)
+ snd_hdmi_show_eld(codec, &eld->info);
+
+ eld_changed = (pin_eld->eld_valid != eld->eld_valid);
+ eld_changed |= (pin_eld->monitor_present != eld->monitor_present);
+ if (!eld_changed && eld->eld_valid && pin_eld->eld_valid)
+ if (pin_eld->eld_size != eld->eld_size ||
+ memcmp(pin_eld->eld_buffer, eld->eld_buffer,
+ eld->eld_size) != 0)
+ eld_changed = true;
+
+ if (eld_changed) {
+ pin_eld->monitor_present = eld->monitor_present;
+ pin_eld->eld_valid = eld->eld_valid;
+ pin_eld->eld_size = eld->eld_size;
+ if (eld->eld_valid)
+ memcpy(pin_eld->eld_buffer, eld->eld_buffer,
+ eld->eld_size);
+ pin_eld->info = eld->info;
+ }
+
+ /*
+ * Re-setup pin and infoframe. This is needed e.g. when
+ * - sink is first plugged-in
+ * - transcoder can change during stream playback on Haswell
+ * and this can make HW reset converter selection on a pin.
+ */
+ if (eld->eld_valid && !old_eld_valid && per_pin->setup) {
+ pin_cvt_fixup(codec, per_pin, 0);
+ hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
+ }
+
+ if (eld_changed && pcm_idx >= 0)
+ snd_ctl_notify(codec->card,
+ SNDRV_CTL_EVENT_MASK_VALUE |
+ SNDRV_CTL_EVENT_MASK_INFO,
+ &get_hdmi_pcm(spec, pcm_idx)->eld_ctl->id);
+
+ if (eld_changed && pcm_jack)
+ snd_jack_report(pcm_jack,
+ (eld->monitor_present && eld->eld_valid) ?
+ SND_JACK_AVOUT : 0);
+}
+
+/* update ELD and jack state via HD-audio verbs */
+static void hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
+ int repoll)
+{
+ struct hda_codec *codec = per_pin->codec;
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_eld *eld = &spec->temp_eld;
+ struct device *dev = hda_codec_dev(codec);
+ hda_nid_t pin_nid = per_pin->pin_nid;
+ int dev_id = per_pin->dev_id;
+ /*
+ * Always execute a GetPinSense verb here, even when called from
+ * hdmi_intrinsic_event; for some NVIDIA HW, the unsolicited
+ * response's PD bit is not the real PD value, but indicates that
+ * the real PD value changed. An older version of the HD-audio
+ * specification worked this way. Hence, we just ignore the data in
+ * the unsolicited response to avoid custom WARs.
+ */
+ int present;
+ int ret;
+
+#ifdef CONFIG_PM
+ if (dev->power.runtime_status == RPM_SUSPENDING)
+ return;
+#endif
+
+ ret = snd_hda_power_up_pm(codec);
+ if (ret < 0 && pm_runtime_suspended(dev))
+ goto out;
+
+ present = snd_hda_jack_pin_sense(codec, pin_nid, dev_id);
+
+ mutex_lock(&per_pin->lock);
+ eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
+ if (eld->monitor_present)
+ eld->eld_valid = !!(present & AC_PINSENSE_ELDV);
+ else
+ eld->eld_valid = false;
+
+ codec_dbg(codec,
+ "HDMI status: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
+ codec->addr, pin_nid, eld->monitor_present, eld->eld_valid);
+
+ if (eld->eld_valid) {
+ if (spec->ops.pin_get_eld(codec, pin_nid, dev_id,
+ eld->eld_buffer, &eld->eld_size) < 0)
+ eld->eld_valid = false;
+ }
+
+ update_eld(codec, per_pin, eld, repoll);
+ mutex_unlock(&per_pin->lock);
+ out:
+ snd_hda_power_down_pm(codec);
+}
+
+#define I915_SILENT_RATE 48000
+#define I915_SILENT_CHANNELS 2
+#define I915_SILENT_FORMAT SNDRV_PCM_FORMAT_S16_LE
+#define I915_SILENT_FORMAT_BITS 16
+#define I915_SILENT_FMT_MASK 0xf
+
+static void silent_stream_enable(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_cvt *per_cvt;
+ int cvt_idx, pin_idx, err;
+ unsigned int format;
+
+ mutex_lock(&per_pin->lock);
+
+ if (per_pin->setup) {
+ codec_dbg(codec, "hdmi: PCM already open, no silent stream\n");
+ goto unlock_out;
+ }
+
+ pin_idx = pin_id_to_pin_index(codec, per_pin->pin_nid, per_pin->dev_id);
+ err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx);
+ if (err) {
+ codec_err(codec, "hdmi: no free converter to enable silent mode\n");
+ goto unlock_out;
+ }
+
+ per_cvt = get_cvt(spec, cvt_idx);
+ per_cvt->assigned = 1;
+ per_pin->cvt_nid = per_cvt->cvt_nid;
+ per_pin->silent_stream = true;
+
+ codec_dbg(codec, "hdmi: enabling silent stream pin-NID=0x%x cvt-NID=0x%x\n",
+ per_pin->pin_nid, per_cvt->cvt_nid);
+
+ snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id);
+ snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
+ AC_VERB_SET_CONNECT_SEL,
+ per_pin->mux_idx);
+
+ /* configure unused pins to choose other converters */
+ pin_cvt_fixup(codec, per_pin, 0);
+
+ snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid,
+ per_pin->dev_id, I915_SILENT_RATE);
+
+ /* trigger silent stream generation in hw */
+ format = snd_hdac_calc_stream_format(I915_SILENT_RATE, I915_SILENT_CHANNELS,
+ I915_SILENT_FORMAT, I915_SILENT_FORMAT_BITS, 0);
+ snd_hda_codec_setup_stream(codec, per_pin->cvt_nid,
+ I915_SILENT_FMT_MASK, I915_SILENT_FMT_MASK, format);
+ usleep_range(100, 200);
+ snd_hda_codec_setup_stream(codec, per_pin->cvt_nid, I915_SILENT_FMT_MASK, 0, format);
+
+ per_pin->channels = I915_SILENT_CHANNELS;
+ hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
+
+ unlock_out:
+ mutex_unlock(&per_pin->lock);
+}
+
+static void silent_stream_disable(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_cvt *per_cvt;
+ int cvt_idx;
+
+ mutex_lock(&per_pin->lock);
+ if (!per_pin->silent_stream)
+ goto unlock_out;
+
+ codec_dbg(codec, "HDMI: disable silent stream on pin-NID=0x%x cvt-NID=0x%x\n",
+ per_pin->pin_nid, per_pin->cvt_nid);
+
+ cvt_idx = cvt_nid_to_cvt_index(codec, per_pin->cvt_nid);
+ if (cvt_idx >= 0 && cvt_idx < spec->num_cvts) {
+ per_cvt = get_cvt(spec, cvt_idx);
+ per_cvt->assigned = 0;
+ }
+
+ per_pin->cvt_nid = 0;
+ per_pin->silent_stream = false;
+
+ unlock_out:
+ mutex_unlock(&per_pin->lock);
+}
+
+/* update ELD and jack state via audio component */
+static void sync_eld_via_acomp(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_eld *eld = &spec->temp_eld;
+ bool monitor_prev, monitor_next;
+
+ mutex_lock(&per_pin->lock);
+ eld->monitor_present = false;
+ monitor_prev = per_pin->sink_eld.monitor_present;
+ eld->eld_size = snd_hdac_acomp_get_eld(&codec->core, per_pin->pin_nid,
+ per_pin->dev_id, &eld->monitor_present,
+ eld->eld_buffer, ELD_MAX_SIZE);
+ eld->eld_valid = (eld->eld_size > 0);
+ update_eld(codec, per_pin, eld, 0);
+ monitor_next = per_pin->sink_eld.monitor_present;
+ mutex_unlock(&per_pin->lock);
+
+ /*
+ * Power-up will call hdmi_present_sense, so the PM calls
+ * have to be done without mutex held.
+ */
+
+ if (spec->send_silent_stream) {
+ int pm_ret;
+
+ if (!monitor_prev && monitor_next) {
+ pm_ret = snd_hda_power_up_pm(codec);
+ if (pm_ret < 0)
+ codec_err(codec,
+ "Monitor plugged-in, Failed to power up codec ret=[%d]\n",
+ pm_ret);
+ silent_stream_enable(codec, per_pin);
+ } else if (monitor_prev && !monitor_next) {
+ silent_stream_disable(codec, per_pin);
+ pm_ret = snd_hda_power_down_pm(codec);
+ if (pm_ret < 0)
+ codec_err(codec,
+ "Monitor plugged-out, Failed to power down codec ret=[%d]\n",
+ pm_ret);
+ }
+ }
+}
+
+static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
+{
+ struct hda_codec *codec = per_pin->codec;
+
+ if (!codec_has_acomp(codec))
+ hdmi_present_sense_via_verbs(per_pin, repoll);
+ else
+ sync_eld_via_acomp(codec, per_pin);
+}
+
+static void hdmi_repoll_eld(struct work_struct *work)
+{
+ struct hdmi_spec_per_pin *per_pin =
+ container_of(to_delayed_work(work), struct hdmi_spec_per_pin, work);
+ struct hda_codec *codec = per_pin->codec;
+ struct hdmi_spec *spec = codec->spec;
+ struct hda_jack_tbl *jack;
+
+ jack = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid,
+ per_pin->dev_id);
+ if (jack)
+ jack->jack_dirty = 1;
+
+ if (per_pin->repoll_count++ > 6)
+ per_pin->repoll_count = 0;
+
+ mutex_lock(&spec->pcm_lock);
+ hdmi_present_sense(per_pin, per_pin->repoll_count);
+ mutex_unlock(&spec->pcm_lock);
+}
+
+static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+ struct hdmi_spec *spec = codec->spec;
+ unsigned int caps, config;
+ int pin_idx;
+ struct hdmi_spec_per_pin *per_pin;
+ int err;
+ int dev_num, i;
+
+ caps = snd_hda_query_pin_caps(codec, pin_nid);
+ if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP)))
+ return 0;
+
+ /*
+ * For DP MST audio, Configuration Default is the same for
+ * all device entries on the same pin
+ */
+ config = snd_hda_codec_get_pincfg(codec, pin_nid);
+ if (get_defcfg_connect(config) == AC_JACK_PORT_NONE &&
+ !spec->force_connect)
+ return 0;
+
+ /*
+ * To simplify the implementation, malloc all
+ * the virtual pins in the initialization statically
+ */
+ if (spec->intel_hsw_fixup) {
+ /*
+ * On Intel platforms, device entries number is
+ * changed dynamically. If there is a DP MST
+ * hub connected, the device entries number is 3.
+ * Otherwise, it is 1.
+ * Here we manually set dev_num to 3, so that
+ * we can initialize all the device entries when
+ * bootup statically.
+ */
+ dev_num = 3;
+ spec->dev_num = 3;
+ } else if (spec->dyn_pcm_assign && codec->dp_mst) {
+ dev_num = snd_hda_get_num_devices(codec, pin_nid) + 1;
+ /*
+ * spec->dev_num is the maxinum number of device entries
+ * among all the pins
+ */
+ spec->dev_num = (spec->dev_num > dev_num) ?
+ spec->dev_num : dev_num;
+ } else {
+ /*
+ * If the platform doesn't support DP MST,
+ * manually set dev_num to 1. This means
+ * the pin has only one device entry.
+ */
+ dev_num = 1;
+ spec->dev_num = 1;
+ }
+
+ for (i = 0; i < dev_num; i++) {
+ pin_idx = spec->num_pins;
+ per_pin = snd_array_new(&spec->pins);
+
+ if (!per_pin)
+ return -ENOMEM;
+
+ if (spec->dyn_pcm_assign) {
+ per_pin->pcm = NULL;
+ per_pin->pcm_idx = -1;
+ } else {
+ per_pin->pcm = get_hdmi_pcm(spec, pin_idx);
+ per_pin->pcm_idx = pin_idx;
+ }
+ per_pin->pin_nid = pin_nid;
+ per_pin->pin_nid_idx = spec->num_nids;
+ per_pin->dev_id = i;
+ per_pin->non_pcm = false;
+ snd_hda_set_dev_select(codec, pin_nid, i);
+ err = hdmi_read_pin_conn(codec, pin_idx);
+ if (err < 0)
+ return err;
+ spec->num_pins++;
+ }
+ spec->num_nids++;
+
+ return 0;
+}
+
+static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_cvt *per_cvt;
+ unsigned int chans;
+ int err;
+
+ chans = get_wcaps(codec, cvt_nid);
+ chans = get_wcaps_channels(chans);
+
+ per_cvt = snd_array_new(&spec->cvts);
+ if (!per_cvt)
+ return -ENOMEM;
+
+ per_cvt->cvt_nid = cvt_nid;
+ per_cvt->channels_min = 2;
+ if (chans <= 16) {
+ per_cvt->channels_max = chans;
+ if (chans > spec->chmap.channels_max)
+ spec->chmap.channels_max = chans;
+ }
+
+ err = snd_hda_query_supported_pcm(codec, cvt_nid,
+ &per_cvt->rates,
+ &per_cvt->formats,
+ &per_cvt->maxbps);
+ if (err < 0)
+ return err;
+
+ if (spec->num_cvts < ARRAY_SIZE(spec->cvt_nids))
+ spec->cvt_nids[spec->num_cvts] = cvt_nid;
+ spec->num_cvts++;
+
+ return 0;
+}
+
+static const struct snd_pci_quirk force_connect_list[] = {
+ SND_PCI_QUIRK(0x103c, 0x870f, "HP", 1),
+ SND_PCI_QUIRK(0x103c, 0x871a, "HP", 1),
+ SND_PCI_QUIRK(0x103c, 0x8711, "HP", 1),
+ SND_PCI_QUIRK(0x103c, 0x8715, "HP", 1),
+ SND_PCI_QUIRK(0x1043, 0x86ae, "ASUS", 1), /* Z170 PRO */
+ SND_PCI_QUIRK(0x1043, 0x86c7, "ASUS", 1), /* Z170M PLUS */
+ SND_PCI_QUIRK(0x1462, 0xec94, "MS-7C94", 1),
+ SND_PCI_QUIRK(0x8086, 0x2060, "Intel NUC5CPYB", 1),
+ SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", 1),
+ {}
+};
+
+static int hdmi_parse_codec(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ hda_nid_t start_nid;
+ unsigned int caps;
+ int i, nodes;
+ const struct snd_pci_quirk *q;
+
+ nodes = snd_hda_get_sub_nodes(codec, codec->core.afg, &start_nid);
+ if (!start_nid || nodes < 0) {
+ codec_warn(codec, "HDMI: failed to get afg sub nodes\n");
+ return -EINVAL;
+ }
+
+ q = snd_pci_quirk_lookup(codec->bus->pci, force_connect_list);
+
+ if (q && q->value)
+ spec->force_connect = true;
+
+ /*
+ * hdmi_add_pin() assumes total amount of converters to
+ * be known, so first discover all converters
+ */
+ for (i = 0; i < nodes; i++) {
+ hda_nid_t nid = start_nid + i;
+
+ caps = get_wcaps(codec, nid);
+
+ if (!(caps & AC_WCAP_DIGITAL))
+ continue;
+
+ if (get_wcaps_type(caps) == AC_WID_AUD_OUT)
+ hdmi_add_cvt(codec, nid);
+ }
+
+ /* discover audio pins */
+ for (i = 0; i < nodes; i++) {
+ hda_nid_t nid = start_nid + i;
+
+ caps = get_wcaps(codec, nid);
+
+ if (!(caps & AC_WCAP_DIGITAL))
+ continue;
+
+ if (get_wcaps_type(caps) == AC_WID_PIN)
+ hdmi_add_pin(codec, nid);
+ }
+
+ return 0;
+}
+
+/*
+ */
+static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid)
+{
+ struct hda_spdif_out *spdif;
+ bool non_pcm;
+
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_hda_spdif_out_of_nid(codec, cvt_nid);
+ /* Add sanity check to pass klockwork check.
+ * This should never happen.
+ */
+ if (WARN_ON(spdif == NULL)) {
+ mutex_unlock(&codec->spdif_mutex);
+ return true;
+ }
+ non_pcm = !!(spdif->status & IEC958_AES0_NONAUDIO);
+ mutex_unlock(&codec->spdif_mutex);
+ return non_pcm;
+}
+
+/*
+ * HDMI callbacks
+ */
+
+static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ hda_nid_t cvt_nid = hinfo->nid;
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+ struct hdmi_spec_per_pin *per_pin;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ bool non_pcm;
+ int pinctl, stripe;
+ int err = 0;
+
+ mutex_lock(&spec->pcm_lock);
+ pin_idx = hinfo_to_pin_index(codec, hinfo);
+ if (spec->dyn_pcm_assign && pin_idx < 0) {
+ /* when dyn_pcm_assign and pcm is not bound to a pin
+ * skip pin setup and return 0 to make audio playback
+ * be ongoing
+ */
+ pin_cvt_fixup(codec, NULL, cvt_nid);
+ snd_hda_codec_setup_stream(codec, cvt_nid,
+ stream_tag, 0, format);
+ goto unlock;
+ }
+
+ if (snd_BUG_ON(pin_idx < 0)) {
+ err = -EINVAL;
+ goto unlock;
+ }
+ per_pin = get_pin(spec, pin_idx);
+
+ /* Verify pin:cvt selections to avoid silent audio after S3.
+ * After S3, the audio driver restores pin:cvt selections
+ * but this can happen before gfx is ready and such selection
+ * is overlooked by HW. Thus multiple pins can share a same
+ * default convertor and mute control will affect each other,
+ * which can cause a resumed audio playback become silent
+ * after S3.
+ */
+ pin_cvt_fixup(codec, per_pin, 0);
+
+ /* Call sync_audio_rate to set the N/CTS/M manually if necessary */
+ /* Todo: add DP1.2 MST audio support later */
+ if (codec_has_acomp(codec))
+ snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid,
+ per_pin->dev_id, runtime->rate);
+
+ non_pcm = check_non_pcm_per_cvt(codec, cvt_nid);
+ mutex_lock(&per_pin->lock);
+ per_pin->channels = substream->runtime->channels;
+ per_pin->setup = true;
+
+ if (get_wcaps(codec, cvt_nid) & AC_WCAP_STRIPE) {
+ stripe = snd_hdac_get_stream_stripe_ctl(&codec->bus->core,
+ substream);
+ snd_hda_codec_write(codec, cvt_nid, 0,
+ AC_VERB_SET_STRIPE_CONTROL,
+ stripe);
+ }
+
+ hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
+ mutex_unlock(&per_pin->lock);
+ if (spec->dyn_pin_out) {
+ snd_hda_set_dev_select(codec, per_pin->pin_nid,
+ per_pin->dev_id);
+ pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ pinctl | PIN_OUT);
+ }
+
+ /* snd_hda_set_dev_select() has been called before */
+ err = spec->ops.setup_stream(codec, cvt_nid, per_pin->pin_nid,
+ per_pin->dev_id, stream_tag, format);
+ unlock:
+ mutex_unlock(&spec->pcm_lock);
+ return err;
+}
+
+static int generic_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ return 0;
+}
+
+static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int cvt_idx, pin_idx, pcm_idx;
+ struct hdmi_spec_per_cvt *per_cvt;
+ struct hdmi_spec_per_pin *per_pin;
+ int pinctl;
+ int err = 0;
+
+ mutex_lock(&spec->pcm_lock);
+ if (hinfo->nid) {
+ pcm_idx = hinfo_to_pcm_index(codec, hinfo);
+ if (snd_BUG_ON(pcm_idx < 0)) {
+ err = -EINVAL;
+ goto unlock;
+ }
+ cvt_idx = cvt_nid_to_cvt_index(codec, hinfo->nid);
+ if (snd_BUG_ON(cvt_idx < 0)) {
+ err = -EINVAL;
+ goto unlock;
+ }
+ per_cvt = get_cvt(spec, cvt_idx);
+ per_cvt->assigned = 0;
+ hinfo->nid = 0;
+
+ azx_stream(get_azx_dev(substream))->stripe = 0;
+
+ snd_hda_spdif_ctls_unassign(codec, pcm_idx);
+ clear_bit(pcm_idx, &spec->pcm_in_use);
+ pin_idx = hinfo_to_pin_index(codec, hinfo);
+ if (spec->dyn_pcm_assign && pin_idx < 0)
+ goto unlock;
+
+ if (snd_BUG_ON(pin_idx < 0)) {
+ err = -EINVAL;
+ goto unlock;
+ }
+ per_pin = get_pin(spec, pin_idx);
+
+ if (spec->dyn_pin_out) {
+ snd_hda_set_dev_select(codec, per_pin->pin_nid,
+ per_pin->dev_id);
+ pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ pinctl & ~PIN_OUT);
+ }
+
+ mutex_lock(&per_pin->lock);
+ per_pin->chmap_set = false;
+ memset(per_pin->chmap, 0, sizeof(per_pin->chmap));
+
+ per_pin->setup = false;
+ per_pin->channels = 0;
+ mutex_unlock(&per_pin->lock);
+ }
+
+unlock:
+ mutex_unlock(&spec->pcm_lock);
+
+ return err;
+}
+
+static const struct hda_pcm_ops generic_ops = {
+ .open = hdmi_pcm_open,
+ .close = hdmi_pcm_close,
+ .prepare = generic_hdmi_playback_pcm_prepare,
+ .cleanup = generic_hdmi_playback_pcm_cleanup,
+};
+
+static int hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx)
+{
+ struct hda_codec *codec = hdac_to_hda_codec(hdac);
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx);
+
+ if (!per_pin)
+ return 0;
+
+ return per_pin->sink_eld.info.spk_alloc;
+}
+
+static void hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx,
+ unsigned char *chmap)
+{
+ struct hda_codec *codec = hdac_to_hda_codec(hdac);
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx);
+
+ /* chmap is already set to 0 in caller */
+ if (!per_pin)
+ return;
+
+ memcpy(chmap, per_pin->chmap, ARRAY_SIZE(per_pin->chmap));
+}
+
+static void hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx,
+ unsigned char *chmap, int prepared)
+{
+ struct hda_codec *codec = hdac_to_hda_codec(hdac);
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx);
+
+ if (!per_pin)
+ return;
+ mutex_lock(&per_pin->lock);
+ per_pin->chmap_set = true;
+ memcpy(per_pin->chmap, chmap, ARRAY_SIZE(per_pin->chmap));
+ if (prepared)
+ hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
+ mutex_unlock(&per_pin->lock);
+}
+
+static bool is_hdmi_pcm_attached(struct hdac_device *hdac, int pcm_idx)
+{
+ struct hda_codec *codec = hdac_to_hda_codec(hdac);
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx);
+
+ return per_pin ? true:false;
+}
+
+static int generic_hdmi_build_pcms(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int idx, pcm_num;
+
+ /*
+ * for non-mst mode, pcm number is the same as before
+ * for DP MST mode without extra PCM, pcm number is same
+ * for DP MST mode with extra PCMs, pcm number is
+ * (nid number + dev_num - 1)
+ * dev_num is the device entry number in a pin
+ */
+
+ if (spec->dyn_pcm_no_legacy && codec->mst_no_extra_pcms)
+ pcm_num = spec->num_cvts;
+ else if (codec->mst_no_extra_pcms)
+ pcm_num = spec->num_nids;
+ else
+ pcm_num = spec->num_nids + spec->dev_num - 1;
+
+ codec_dbg(codec, "hdmi: pcm_num set to %d\n", pcm_num);
+
+ for (idx = 0; idx < pcm_num; idx++) {
+ struct hda_pcm *info;
+ struct hda_pcm_stream *pstr;
+
+ info = snd_hda_codec_pcm_new(codec, "HDMI %d", idx);
+ if (!info)
+ return -ENOMEM;
+
+ spec->pcm_rec[idx].pcm = info;
+ spec->pcm_used++;
+ info->pcm_type = HDA_PCM_TYPE_HDMI;
+ info->own_chmap = true;
+
+ pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
+ pstr->substreams = 1;
+ pstr->ops = generic_ops;
+ /* pcm number is less than 16 */
+ if (spec->pcm_used >= 16)
+ break;
+ /* other pstr fields are set in open */
+ }
+
+ return 0;
+}
+
+static void free_hdmi_jack_priv(struct snd_jack *jack)
+{
+ struct hdmi_pcm *pcm = jack->private_data;
+
+ pcm->jack = NULL;
+}
+
+static int generic_hdmi_build_jack(struct hda_codec *codec, int pcm_idx)
+{
+ char hdmi_str[32] = "HDMI/DP";
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pcm_idx);
+ struct snd_jack *jack;
+ int pcmdev = get_pcm_rec(spec, pcm_idx)->device;
+ int err;
+
+ if (pcmdev > 0)
+ sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev);
+ if (!spec->dyn_pcm_assign &&
+ !is_jack_detectable(codec, per_pin->pin_nid))
+ strncat(hdmi_str, " Phantom",
+ sizeof(hdmi_str) - strlen(hdmi_str) - 1);
+
+ err = snd_jack_new(codec->card, hdmi_str, SND_JACK_AVOUT, &jack,
+ true, false);
+ if (err < 0)
+ return err;
+
+ spec->pcm_rec[pcm_idx].jack = jack;
+ jack->private_data = &spec->pcm_rec[pcm_idx];
+ jack->private_free = free_hdmi_jack_priv;
+ return 0;
+}
+
+static int generic_hdmi_build_controls(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int dev, err;
+ int pin_idx, pcm_idx;
+
+ for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) {
+ if (!get_pcm_rec(spec, pcm_idx)->pcm) {
+ /* no PCM: mark this for skipping permanently */
+ set_bit(pcm_idx, &spec->pcm_bitmap);
+ continue;
+ }
+
+ err = generic_hdmi_build_jack(codec, pcm_idx);
+ if (err < 0)
+ return err;
+
+ /* create the spdif for each pcm
+ * pin will be bound when monitor is connected
+ */
+ if (spec->dyn_pcm_assign)
+ err = snd_hda_create_dig_out_ctls(codec,
+ 0, spec->cvt_nids[0],
+ HDA_PCM_TYPE_HDMI);
+ else {
+ struct hdmi_spec_per_pin *per_pin =
+ get_pin(spec, pcm_idx);
+ err = snd_hda_create_dig_out_ctls(codec,
+ per_pin->pin_nid,
+ per_pin->mux_nids[0],
+ HDA_PCM_TYPE_HDMI);
+ }
+ if (err < 0)
+ return err;
+ snd_hda_spdif_ctls_unassign(codec, pcm_idx);
+
+ dev = get_pcm_rec(spec, pcm_idx)->device;
+ if (dev != SNDRV_PCM_INVALID_DEVICE) {
+ /* add control for ELD Bytes */
+ err = hdmi_create_eld_ctl(codec, pcm_idx, dev);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ struct hdmi_eld *pin_eld = &per_pin->sink_eld;
+
+ pin_eld->eld_valid = false;
+ hdmi_present_sense(per_pin, 0);
+ }
+
+ /* add channel maps */
+ for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) {
+ struct hda_pcm *pcm;
+
+ pcm = get_pcm_rec(spec, pcm_idx);
+ if (!pcm || !pcm->pcm)
+ break;
+ err = snd_hdac_add_chmap_ctls(pcm->pcm, pcm_idx, &spec->chmap);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int generic_hdmi_init_per_pins(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+
+ per_pin->codec = codec;
+ mutex_init(&per_pin->lock);
+ INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld);
+ eld_proc_new(per_pin, pin_idx);
+ }
+ return 0;
+}
+
+static int generic_hdmi_init(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+
+ mutex_lock(&spec->bind_lock);
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ hda_nid_t pin_nid = per_pin->pin_nid;
+ int dev_id = per_pin->dev_id;
+
+ snd_hda_set_dev_select(codec, pin_nid, dev_id);
+ hdmi_init_pin(codec, pin_nid);
+ if (codec_has_acomp(codec))
+ continue;
+ snd_hda_jack_detect_enable_callback_mst(codec, pin_nid, dev_id,
+ jack_callback);
+ }
+ mutex_unlock(&spec->bind_lock);
+ return 0;
+}
+
+static void hdmi_array_init(struct hdmi_spec *spec, int nums)
+{
+ snd_array_init(&spec->pins, sizeof(struct hdmi_spec_per_pin), nums);
+ snd_array_init(&spec->cvts, sizeof(struct hdmi_spec_per_cvt), nums);
+}
+
+static void hdmi_array_free(struct hdmi_spec *spec)
+{
+ snd_array_free(&spec->pins);
+ snd_array_free(&spec->cvts);
+}
+
+static void generic_spec_free(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ if (spec) {
+ hdmi_array_free(spec);
+ kfree(spec);
+ codec->spec = NULL;
+ }
+ codec->dp_mst = false;
+}
+
+static void generic_hdmi_free(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx, pcm_idx;
+
+ if (spec->acomp_registered) {
+ snd_hdac_acomp_exit(&codec->bus->core);
+ } else if (codec_has_acomp(codec)) {
+ snd_hdac_acomp_register_notifier(&codec->bus->core, NULL);
+ }
+ codec->relaxed_resume = 0;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ cancel_delayed_work_sync(&per_pin->work);
+ eld_proc_free(per_pin);
+ }
+
+ for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) {
+ if (spec->pcm_rec[pcm_idx].jack == NULL)
+ continue;
+ if (spec->dyn_pcm_assign)
+ snd_device_free(codec->card,
+ spec->pcm_rec[pcm_idx].jack);
+ else
+ spec->pcm_rec[pcm_idx].jack = NULL;
+ }
+
+ generic_spec_free(codec);
+}
+
+#ifdef CONFIG_PM
+static int generic_hdmi_suspend(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ cancel_delayed_work_sync(&per_pin->work);
+ }
+ return 0;
+}
+
+static int generic_hdmi_resume(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+
+ codec->patch_ops.init(codec);
+ snd_hda_regmap_sync(codec);
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ hdmi_present_sense(per_pin, 1);
+ }
+ return 0;
+}
+#endif
+
+static const struct hda_codec_ops generic_hdmi_patch_ops = {
+ .init = generic_hdmi_init,
+ .free = generic_hdmi_free,
+ .build_pcms = generic_hdmi_build_pcms,
+ .build_controls = generic_hdmi_build_controls,
+ .unsol_event = hdmi_unsol_event,
+#ifdef CONFIG_PM
+ .suspend = generic_hdmi_suspend,
+ .resume = generic_hdmi_resume,
+#endif
+};
+
+static const struct hdmi_ops generic_standard_hdmi_ops = {
+ .pin_get_eld = hdmi_pin_get_eld,
+ .pin_setup_infoframe = hdmi_pin_setup_infoframe,
+ .pin_hbr_setup = hdmi_pin_hbr_setup,
+ .setup_stream = hdmi_setup_stream,
+};
+
+/* allocate codec->spec and assign/initialize generic parser ops */
+static int alloc_generic_hdmi(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+
+ spec->codec = codec;
+ spec->ops = generic_standard_hdmi_ops;
+ spec->dev_num = 1; /* initialize to 1 */
+ mutex_init(&spec->pcm_lock);
+ mutex_init(&spec->bind_lock);
+ snd_hdac_register_chmap_ops(&codec->core, &spec->chmap);
+
+ spec->chmap.ops.get_chmap = hdmi_get_chmap;
+ spec->chmap.ops.set_chmap = hdmi_set_chmap;
+ spec->chmap.ops.is_pcm_attached = is_hdmi_pcm_attached;
+ spec->chmap.ops.get_spk_alloc = hdmi_get_spk_alloc;
+
+ codec->spec = spec;
+ hdmi_array_init(spec, 4);
+
+ codec->patch_ops = generic_hdmi_patch_ops;
+
+ return 0;
+}
+
+/* generic HDMI parser */
+static int patch_generic_hdmi(struct hda_codec *codec)
+{
+ int err;
+
+ err = alloc_generic_hdmi(codec);
+ if (err < 0)
+ return err;
+
+ err = hdmi_parse_codec(codec);
+ if (err < 0) {
+ generic_spec_free(codec);
+ return err;
+ }
+
+ generic_hdmi_init_per_pins(codec);
+ return 0;
+}
+
+/*
+ * generic audio component binding
+ */
+
+/* turn on / off the unsol event jack detection dynamically */
+static void reprogram_jack_detect(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, bool use_acomp)
+{
+ struct hda_jack_tbl *tbl;
+
+ tbl = snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ if (tbl) {
+ /* clear unsol even if component notifier is used, or re-enable
+ * if notifier is cleared
+ */
+ unsigned int val = use_acomp ? 0 : (AC_USRSP_EN | tbl->tag);
+ snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE, val);
+ }
+}
+
+/* set up / clear component notifier dynamically */
+static void generic_acomp_notifier_set(struct drm_audio_component *acomp,
+ bool use_acomp)
+{
+ struct hdmi_spec *spec;
+ int i;
+
+ spec = container_of(acomp->audio_ops, struct hdmi_spec, drm_audio_ops);
+ mutex_lock(&spec->bind_lock);
+ spec->use_acomp_notifier = use_acomp;
+ spec->codec->relaxed_resume = use_acomp;
+ spec->codec->bus->keep_power = 0;
+ /* reprogram each jack detection logic depending on the notifier */
+ for (i = 0; i < spec->num_pins; i++)
+ reprogram_jack_detect(spec->codec,
+ get_pin(spec, i)->pin_nid,
+ get_pin(spec, i)->dev_id,
+ use_acomp);
+ mutex_unlock(&spec->bind_lock);
+}
+
+/* enable / disable the notifier via master bind / unbind */
+static int generic_acomp_master_bind(struct device *dev,
+ struct drm_audio_component *acomp)
+{
+ generic_acomp_notifier_set(acomp, true);
+ return 0;
+}
+
+static void generic_acomp_master_unbind(struct device *dev,
+ struct drm_audio_component *acomp)
+{
+ generic_acomp_notifier_set(acomp, false);
+}
+
+/* check whether both HD-audio and DRM PCI devices belong to the same bus */
+static int match_bound_vga(struct device *dev, int subtype, void *data)
+{
+ struct hdac_bus *bus = data;
+ struct pci_dev *pci, *master;
+
+ if (!dev_is_pci(dev) || !dev_is_pci(bus->dev))
+ return 0;
+ master = to_pci_dev(bus->dev);
+ pci = to_pci_dev(dev);
+ return master->bus == pci->bus;
+}
+
+/* audio component notifier for AMD/Nvidia HDMI codecs */
+static void generic_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id)
+{
+ struct hda_codec *codec = audio_ptr;
+ struct hdmi_spec *spec = codec->spec;
+ hda_nid_t pin_nid = spec->port2pin(codec, port);
+
+ if (!pin_nid)
+ return;
+ if (get_wcaps_type(get_wcaps(codec, pin_nid)) != AC_WID_PIN)
+ return;
+ /* skip notification during system suspend (but not in runtime PM);
+ * the state will be updated at resume
+ */
+ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND)
+ return;
+
+ check_presence_and_report(codec, pin_nid, dev_id);
+}
+
+/* set up the private drm_audio_ops from the template */
+static void setup_drm_audio_ops(struct hda_codec *codec,
+ const struct drm_audio_component_audio_ops *ops)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ spec->drm_audio_ops.audio_ptr = codec;
+ /* intel_audio_codec_enable() or intel_audio_codec_disable()
+ * will call pin_eld_notify with using audio_ptr pointer
+ * We need make sure audio_ptr is really setup
+ */
+ wmb();
+ spec->drm_audio_ops.pin2port = ops->pin2port;
+ spec->drm_audio_ops.pin_eld_notify = ops->pin_eld_notify;
+ spec->drm_audio_ops.master_bind = ops->master_bind;
+ spec->drm_audio_ops.master_unbind = ops->master_unbind;
+}
+
+/* initialize the generic HDMI audio component */
+static void generic_acomp_init(struct hda_codec *codec,
+ const struct drm_audio_component_audio_ops *ops,
+ int (*port2pin)(struct hda_codec *, int))
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ if (!enable_acomp) {
+ codec_info(codec, "audio component disabled by module option\n");
+ return;
+ }
+
+ spec->port2pin = port2pin;
+ setup_drm_audio_ops(codec, ops);
+ if (!snd_hdac_acomp_init(&codec->bus->core, &spec->drm_audio_ops,
+ match_bound_vga, 0)) {
+ spec->acomp_registered = true;
+ }
+}
+
+/*
+ * Intel codec parsers and helpers
+ */
+
+#define INTEL_GET_VENDOR_VERB 0xf81
+#define INTEL_SET_VENDOR_VERB 0x781
+#define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */
+#define INTEL_EN_ALL_PIN_CVTS 0x01 /* enable 2nd & 3rd pins and convertors */
+
+static void intel_haswell_enable_all_pins(struct hda_codec *codec,
+ bool update_tree)
+{
+ unsigned int vendor_param;
+ struct hdmi_spec *spec = codec->spec;
+
+ vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
+ INTEL_GET_VENDOR_VERB, 0);
+ if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS)
+ return;
+
+ vendor_param |= INTEL_EN_ALL_PIN_CVTS;
+ vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
+ INTEL_SET_VENDOR_VERB, vendor_param);
+ if (vendor_param == -1)
+ return;
+
+ if (update_tree)
+ snd_hda_codec_update_widgets(codec);
+}
+
+static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec)
+{
+ unsigned int vendor_param;
+ struct hdmi_spec *spec = codec->spec;
+
+ vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
+ INTEL_GET_VENDOR_VERB, 0);
+ if (vendor_param == -1 || vendor_param & INTEL_EN_DP12)
+ return;
+
+ /* enable DP1.2 mode */
+ vendor_param |= INTEL_EN_DP12;
+ snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB);
+ snd_hda_codec_write_cache(codec, spec->vendor_nid, 0,
+ INTEL_SET_VENDOR_VERB, vendor_param);
+}
+
+/* Haswell needs to re-issue the vendor-specific verbs before turning to D0.
+ * Otherwise you may get severe h/w communication errors.
+ */
+static void haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+ unsigned int power_state)
+{
+ if (power_state == AC_PWRST_D0) {
+ intel_haswell_enable_all_pins(codec, false);
+ intel_haswell_fixup_enable_dp12(codec);
+ }
+
+ snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state);
+ snd_hda_codec_set_power_to_all(codec, fg, power_state);
+}
+
+/* There is a fixed mapping between audio pin node and display port.
+ * on SNB, IVY, HSW, BSW, SKL, BXT, KBL:
+ * Pin Widget 5 - PORT B (port = 1 in i915 driver)
+ * Pin Widget 6 - PORT C (port = 2 in i915 driver)
+ * Pin Widget 7 - PORT D (port = 3 in i915 driver)
+ *
+ * on VLV, ILK:
+ * Pin Widget 4 - PORT B (port = 1 in i915 driver)
+ * Pin Widget 5 - PORT C (port = 2 in i915 driver)
+ * Pin Widget 6 - PORT D (port = 3 in i915 driver)
+ */
+static int intel_base_nid(struct hda_codec *codec)
+{
+ switch (codec->core.vendor_id) {
+ case 0x80860054: /* ILK */
+ case 0x80862804: /* ILK */
+ case 0x80862882: /* VLV */
+ return 4;
+ default:
+ return 5;
+ }
+}
+
+static int intel_pin2port(void *audio_ptr, int pin_nid)
+{
+ struct hda_codec *codec = audio_ptr;
+ struct hdmi_spec *spec = codec->spec;
+ int base_nid, i;
+
+ if (!spec->port_num) {
+ base_nid = intel_base_nid(codec);
+ if (WARN_ON(pin_nid < base_nid || pin_nid >= base_nid + 3))
+ return -1;
+ return pin_nid - base_nid + 1;
+ }
+
+ /*
+ * looking for the pin number in the mapping table and return
+ * the index which indicate the port number
+ */
+ for (i = 0; i < spec->port_num; i++) {
+ if (pin_nid == spec->port_map[i])
+ return i;
+ }
+
+ codec_info(codec, "Can't find the HDMI/DP port for pin %d\n", pin_nid);
+ return -1;
+}
+
+static int intel_port2pin(struct hda_codec *codec, int port)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ if (!spec->port_num) {
+ /* we assume only from port-B to port-D */
+ if (port < 1 || port > 3)
+ return 0;
+ return port + intel_base_nid(codec) - 1;
+ }
+
+ if (port < 0 || port >= spec->port_num)
+ return 0;
+ return spec->port_map[port];
+}
+
+static void intel_pin_eld_notify(void *audio_ptr, int port, int pipe)
+{
+ struct hda_codec *codec = audio_ptr;
+ int pin_nid;
+ int dev_id = pipe;
+
+ pin_nid = intel_port2pin(codec, port);
+ if (!pin_nid)
+ return;
+ /* skip notification during system suspend (but not in runtime PM);
+ * the state will be updated at resume
+ */
+ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND)
+ return;
+
+ snd_hdac_i915_set_bclk(&codec->bus->core);
+ check_presence_and_report(codec, pin_nid, dev_id);
+}
+
+static const struct drm_audio_component_audio_ops intel_audio_ops = {
+ .pin2port = intel_pin2port,
+ .pin_eld_notify = intel_pin_eld_notify,
+};
+
+/* register i915 component pin_eld_notify callback */
+static void register_i915_notifier(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ spec->use_acomp_notifier = true;
+ spec->port2pin = intel_port2pin;
+ setup_drm_audio_ops(codec, &intel_audio_ops);
+ snd_hdac_acomp_register_notifier(&codec->bus->core,
+ &spec->drm_audio_ops);
+ /* no need for forcible resume for jack check thanks to notifier */
+ codec->relaxed_resume = 1;
+}
+
+/* setup_stream ops override for HSW+ */
+static int i915_hsw_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
+ hda_nid_t pin_nid, int dev_id, u32 stream_tag,
+ int format)
+{
+ haswell_verify_D0(codec, cvt_nid, pin_nid);
+ return hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id,
+ stream_tag, format);
+}
+
+/* pin_cvt_fixup ops override for HSW+ and VLV+ */
+static void i915_pin_cvt_fixup(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin,
+ hda_nid_t cvt_nid)
+{
+ if (per_pin) {
+ haswell_verify_D0(codec, per_pin->cvt_nid, per_pin->pin_nid);
+ snd_hda_set_dev_select(codec, per_pin->pin_nid,
+ per_pin->dev_id);
+ intel_verify_pin_cvt_connect(codec, per_pin);
+ intel_not_share_assigned_cvt(codec, per_pin->pin_nid,
+ per_pin->dev_id, per_pin->mux_idx);
+ } else {
+ intel_not_share_assigned_cvt_nid(codec, 0, 0, cvt_nid);
+ }
+}
+
+/* precondition and allocation for Intel codecs */
+static int alloc_intel_hdmi(struct hda_codec *codec)
+{
+ int err;
+
+ /* requires i915 binding */
+ if (!codec->bus->core.audio_component) {
+ codec_info(codec, "No i915 binding for Intel HDMI/DP codec\n");
+ /* set probe_id here to prevent generic fallback binding */
+ codec->probe_id = HDA_CODEC_ID_SKIP_PROBE;
+ return -ENODEV;
+ }
+
+ err = alloc_generic_hdmi(codec);
+ if (err < 0)
+ return err;
+ /* no need to handle unsol events */
+ codec->patch_ops.unsol_event = NULL;
+ return 0;
+}
+
+/* parse and post-process for Intel codecs */
+static int parse_intel_hdmi(struct hda_codec *codec)
+{
+ int err, retries = 3;
+
+ do {
+ err = hdmi_parse_codec(codec);
+ } while (err < 0 && retries--);
+
+ if (err < 0) {
+ generic_spec_free(codec);
+ return err;
+ }
+
+ generic_hdmi_init_per_pins(codec);
+ register_i915_notifier(codec);
+ return 0;
+}
+
+/* Intel Haswell and onwards; audio component with eld notifier */
+static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid,
+ const int *port_map, int port_num)
+{
+ struct hdmi_spec *spec;
+ int err;
+
+ err = alloc_intel_hdmi(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+ codec->dp_mst = true;
+ spec->dyn_pcm_assign = true;
+ spec->vendor_nid = vendor_nid;
+ spec->port_map = port_map;
+ spec->port_num = port_num;
+ spec->intel_hsw_fixup = true;
+
+ intel_haswell_enable_all_pins(codec, true);
+ intel_haswell_fixup_enable_dp12(codec);
+
+ codec->display_power_control = 1;
+
+ codec->patch_ops.set_power_state = haswell_set_power_state;
+ codec->depop_delay = 0;
+ codec->auto_runtime_pm = 1;
+
+ spec->ops.setup_stream = i915_hsw_setup_stream;
+ spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
+
+ /*
+ * Enable silent stream feature, if it is enabled via
+ * module param or Kconfig option
+ */
+ if (enable_silent_stream)
+ spec->send_silent_stream = true;
+
+ return parse_intel_hdmi(codec);
+}
+
+static int patch_i915_hsw_hdmi(struct hda_codec *codec)
+{
+ return intel_hsw_common_init(codec, 0x08, NULL, 0);
+}
+
+static int patch_i915_glk_hdmi(struct hda_codec *codec)
+{
+ return intel_hsw_common_init(codec, 0x0b, NULL, 0);
+}
+
+static int patch_i915_icl_hdmi(struct hda_codec *codec)
+{
+ /*
+ * pin to port mapping table where the value indicate the pin number and
+ * the index indicate the port number.
+ */
+ static const int map[] = {0x0, 0x4, 0x6, 0x8, 0xa, 0xb};
+
+ return intel_hsw_common_init(codec, 0x02, map, ARRAY_SIZE(map));
+}
+
+static int patch_i915_tgl_hdmi(struct hda_codec *codec)
+{
+ /*
+ * pin to port mapping table where the value indicate the pin number and
+ * the index indicate the port number.
+ */
+ static const int map[] = {0x4, 0x6, 0x8, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ int ret;
+
+ ret = intel_hsw_common_init(codec, 0x02, map, ARRAY_SIZE(map));
+ if (!ret) {
+ struct hdmi_spec *spec = codec->spec;
+
+ spec->dyn_pcm_no_legacy = true;
+ }
+
+ return ret;
+}
+
+/* Intel Baytrail and Braswell; with eld notifier */
+static int patch_i915_byt_hdmi(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ int err;
+
+ err = alloc_intel_hdmi(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+
+ /* For Valleyview/Cherryview, only the display codec is in the display
+ * power well and can use link_power ops to request/release the power.
+ */
+ codec->display_power_control = 1;
+
+ codec->depop_delay = 0;
+ codec->auto_runtime_pm = 1;
+
+ spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
+
+ return parse_intel_hdmi(codec);
+}
+
+/* Intel IronLake, SandyBridge and IvyBridge; with eld notifier */
+static int patch_i915_cpt_hdmi(struct hda_codec *codec)
+{
+ int err;
+
+ err = alloc_intel_hdmi(codec);
+ if (err < 0)
+ return err;
+ return parse_intel_hdmi(codec);
+}
+
+/*
+ * Shared non-generic implementations
+ */
+
+static int simple_playback_build_pcms(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hda_pcm *info;
+ unsigned int chans;
+ struct hda_pcm_stream *pstr;
+ struct hdmi_spec_per_cvt *per_cvt;
+
+ per_cvt = get_cvt(spec, 0);
+ chans = get_wcaps(codec, per_cvt->cvt_nid);
+ chans = get_wcaps_channels(chans);
+
+ info = snd_hda_codec_pcm_new(codec, "HDMI 0");
+ if (!info)
+ return -ENOMEM;
+ spec->pcm_rec[0].pcm = info;
+ info->pcm_type = HDA_PCM_TYPE_HDMI;
+ pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
+ *pstr = spec->pcm_playback;
+ pstr->nid = per_cvt->cvt_nid;
+ if (pstr->channels_max <= 2 && chans && chans <= 16)
+ pstr->channels_max = chans;
+
+ return 0;
+}
+
+/* unsolicited event for jack sensing */
+static void simple_hdmi_unsol_event(struct hda_codec *codec,
+ unsigned int res)
+{
+ snd_hda_jack_set_dirty_all(codec);
+ snd_hda_jack_report_sync(codec);
+}
+
+/* generic_hdmi_build_jack can be used for simple_hdmi, too,
+ * as long as spec->pins[] is set correctly
+ */
+#define simple_hdmi_build_jack generic_hdmi_build_jack
+
+static int simple_playback_build_controls(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_cvt *per_cvt;
+ int err;
+
+ per_cvt = get_cvt(spec, 0);
+ err = snd_hda_create_dig_out_ctls(codec, per_cvt->cvt_nid,
+ per_cvt->cvt_nid,
+ HDA_PCM_TYPE_HDMI);
+ if (err < 0)
+ return err;
+ return simple_hdmi_build_jack(codec, 0);
+}
+
+static int simple_playback_init(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, 0);
+ hda_nid_t pin = per_pin->pin_nid;
+
+ snd_hda_codec_write(codec, pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+ /* some codecs require to unmute the pin */
+ if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP)
+ snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+ AMP_OUT_UNMUTE);
+ snd_hda_jack_detect_enable(codec, pin, per_pin->dev_id);
+ return 0;
+}
+
+static void simple_playback_free(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ hdmi_array_free(spec);
+ kfree(spec);
+}
+
+/*
+ * Nvidia specific implementations
+ */
+
+#define Nv_VERB_SET_Channel_Allocation 0xF79
+#define Nv_VERB_SET_Info_Frame_Checksum 0xF7A
+#define Nv_VERB_SET_Audio_Protection_On 0xF98
+#define Nv_VERB_SET_Audio_Protection_Off 0xF99
+
+#define nvhdmi_master_con_nid_7x 0x04
+#define nvhdmi_master_pin_nid_7x 0x05
+
+static const hda_nid_t nvhdmi_con_nids_7x[4] = {
+ /*front, rear, clfe, rear_surr */
+ 0x6, 0x8, 0xa, 0xc,
+};
+
+static const struct hda_verb nvhdmi_basic_init_7x_2ch[] = {
+ /* set audio protect on */
+ { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
+ /* enable digital output on pin widget */
+ { 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+ {} /* terminator */
+};
+
+static const struct hda_verb nvhdmi_basic_init_7x_8ch[] = {
+ /* set audio protect on */
+ { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
+ /* enable digital output on pin widget */
+ { 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+ { 0x7, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+ { 0x9, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+ { 0xb, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+ { 0xd, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+ {} /* terminator */
+};
+
+#ifdef LIMITED_RATE_FMT_SUPPORT
+/* support only the safe format and rate */
+#define SUPPORTED_RATES SNDRV_PCM_RATE_48000
+#define SUPPORTED_MAXBPS 16
+#define SUPPORTED_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+#else
+/* support all rates and formats */
+#define SUPPORTED_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)
+#define SUPPORTED_MAXBPS 24
+#define SUPPORTED_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+#endif
+
+static int nvhdmi_7x_init_2ch(struct hda_codec *codec)
+{
+ snd_hda_sequence_write(codec, nvhdmi_basic_init_7x_2ch);
+ return 0;
+}
+
+static int nvhdmi_7x_init_8ch(struct hda_codec *codec)
+{
+ snd_hda_sequence_write(codec, nvhdmi_basic_init_7x_8ch);
+ return 0;
+}
+
+static const unsigned int channels_2_6_8[] = {
+ 2, 6, 8
+};
+
+static const unsigned int channels_2_8[] = {
+ 2, 8
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_2_6_8_channels = {
+ .count = ARRAY_SIZE(channels_2_6_8),
+ .list = channels_2_6_8,
+ .mask = 0,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_2_8_channels = {
+ .count = ARRAY_SIZE(channels_2_8),
+ .list = channels_2_8,
+ .mask = 0,
+};
+
+static int simple_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ const struct snd_pcm_hw_constraint_list *hw_constraints_channels = NULL;
+
+ switch (codec->preset->vendor_id) {
+ case 0x10de0002:
+ case 0x10de0003:
+ case 0x10de0005:
+ case 0x10de0006:
+ hw_constraints_channels = &hw_constraints_2_8_channels;
+ break;
+ case 0x10de0007:
+ hw_constraints_channels = &hw_constraints_2_6_8_channels;
+ break;
+ default:
+ break;
+ }
+
+ if (hw_constraints_channels != NULL) {
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ hw_constraints_channels);
+ } else {
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+ }
+
+ return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int simple_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+ stream_tag, format, substream);
+}
+
+static const struct hda_pcm_stream simple_pcm_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .ops = {
+ .open = simple_playback_pcm_open,
+ .close = simple_playback_pcm_close,
+ .prepare = simple_playback_pcm_prepare
+ },
+};
+
+static const struct hda_codec_ops simple_hdmi_patch_ops = {
+ .build_controls = simple_playback_build_controls,
+ .build_pcms = simple_playback_build_pcms,
+ .init = simple_playback_init,
+ .free = simple_playback_free,
+ .unsol_event = simple_hdmi_unsol_event,
+};
+
+static int patch_simple_hdmi(struct hda_codec *codec,
+ hda_nid_t cvt_nid, hda_nid_t pin_nid)
+{
+ struct hdmi_spec *spec;
+ struct hdmi_spec_per_cvt *per_cvt;
+ struct hdmi_spec_per_pin *per_pin;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+
+ spec->codec = codec;
+ codec->spec = spec;
+ hdmi_array_init(spec, 1);
+
+ spec->multiout.num_dacs = 0; /* no analog */
+ spec->multiout.max_channels = 2;
+ spec->multiout.dig_out_nid = cvt_nid;
+ spec->num_cvts = 1;
+ spec->num_pins = 1;
+ per_pin = snd_array_new(&spec->pins);
+ per_cvt = snd_array_new(&spec->cvts);
+ if (!per_pin || !per_cvt) {
+ simple_playback_free(codec);
+ return -ENOMEM;
+ }
+ per_cvt->cvt_nid = cvt_nid;
+ per_pin->pin_nid = pin_nid;
+ spec->pcm_playback = simple_pcm_playback;
+
+ codec->patch_ops = simple_hdmi_patch_ops;
+
+ return 0;
+}
+
+static void nvhdmi_8ch_7x_set_info_frame_parameters(struct hda_codec *codec,
+ int channels)
+{
+ unsigned int chanmask;
+ int chan = channels ? (channels - 1) : 1;
+
+ switch (channels) {
+ default:
+ case 0:
+ case 2:
+ chanmask = 0x00;
+ break;
+ case 4:
+ chanmask = 0x08;
+ break;
+ case 6:
+ chanmask = 0x0b;
+ break;
+ case 8:
+ chanmask = 0x13;
+ break;
+ }
+
+ /* Set the audio infoframe channel allocation and checksum fields. The
+ * channel count is computed implicitly by the hardware. */
+ snd_hda_codec_write(codec, 0x1, 0,
+ Nv_VERB_SET_Channel_Allocation, chanmask);
+
+ snd_hda_codec_write(codec, 0x1, 0,
+ Nv_VERB_SET_Info_Frame_Checksum,
+ (0x71 - chan - chanmask));
+}
+
+static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int i;
+
+ snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x,
+ 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+ for (i = 0; i < 4; i++) {
+ /* set the stream id */
+ snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
+ AC_VERB_SET_CHANNEL_STREAMID, 0);
+ /* set the stream format */
+ snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
+ AC_VERB_SET_STREAM_FORMAT, 0);
+ }
+
+ /* The audio hardware sends a channel count of 0x7 (8ch) when all the
+ * streams are disabled. */
+ nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8);
+
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ int chs;
+ unsigned int dataDCC2, channel_id;
+ int i;
+ struct hdmi_spec *spec = codec->spec;
+ struct hda_spdif_out *spdif;
+ struct hdmi_spec_per_cvt *per_cvt;
+
+ mutex_lock(&codec->spdif_mutex);
+ per_cvt = get_cvt(spec, 0);
+ spdif = snd_hda_spdif_out_of_nid(codec, per_cvt->cvt_nid);
+
+ chs = substream->runtime->channels;
+
+ dataDCC2 = 0x2;
+
+ /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
+ if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE))
+ snd_hda_codec_write(codec,
+ nvhdmi_master_con_nid_7x,
+ 0,
+ AC_VERB_SET_DIGI_CONVERT_1,
+ spdif->ctls & ~AC_DIG1_ENABLE & 0xff);
+
+ /* set the stream id */
+ snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
+ AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | 0x0);
+
+ /* set the stream format */
+ snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
+ AC_VERB_SET_STREAM_FORMAT, format);
+
+ /* turn on again (if needed) */
+ /* enable and set the channel status audio/data flag */
+ if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE)) {
+ snd_hda_codec_write(codec,
+ nvhdmi_master_con_nid_7x,
+ 0,
+ AC_VERB_SET_DIGI_CONVERT_1,
+ spdif->ctls & 0xff);
+ snd_hda_codec_write(codec,
+ nvhdmi_master_con_nid_7x,
+ 0,
+ AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (chs == 2)
+ channel_id = 0;
+ else
+ channel_id = i * 2;
+
+ /* turn off SPDIF once;
+ *otherwise the IEC958 bits won't be updated
+ */
+ if (codec->spdif_status_reset &&
+ (spdif->ctls & AC_DIG1_ENABLE))
+ snd_hda_codec_write(codec,
+ nvhdmi_con_nids_7x[i],
+ 0,
+ AC_VERB_SET_DIGI_CONVERT_1,
+ spdif->ctls & ~AC_DIG1_ENABLE & 0xff);
+ /* set the stream id */
+ snd_hda_codec_write(codec,
+ nvhdmi_con_nids_7x[i],
+ 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ (stream_tag << 4) | channel_id);
+ /* set the stream format */
+ snd_hda_codec_write(codec,
+ nvhdmi_con_nids_7x[i],
+ 0,
+ AC_VERB_SET_STREAM_FORMAT,
+ format);
+ /* turn on again (if needed) */
+ /* enable and set the channel status audio/data flag */
+ if (codec->spdif_status_reset &&
+ (spdif->ctls & AC_DIG1_ENABLE)) {
+ snd_hda_codec_write(codec,
+ nvhdmi_con_nids_7x[i],
+ 0,
+ AC_VERB_SET_DIGI_CONVERT_1,
+ spdif->ctls & 0xff);
+ snd_hda_codec_write(codec,
+ nvhdmi_con_nids_7x[i],
+ 0,
+ AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
+ }
+ }
+
+ nvhdmi_8ch_7x_set_info_frame_parameters(codec, chs);
+
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+
+static const struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 8,
+ .nid = nvhdmi_master_con_nid_7x,
+ .rates = SUPPORTED_RATES,
+ .maxbps = SUPPORTED_MAXBPS,
+ .formats = SUPPORTED_FORMATS,
+ .ops = {
+ .open = simple_playback_pcm_open,
+ .close = nvhdmi_8ch_7x_pcm_close,
+ .prepare = nvhdmi_8ch_7x_pcm_prepare
+ },
+};
+
+static int patch_nvhdmi_2ch(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ int err = patch_simple_hdmi(codec, nvhdmi_master_con_nid_7x,
+ nvhdmi_master_pin_nid_7x);
+ if (err < 0)
+ return err;
+
+ codec->patch_ops.init = nvhdmi_7x_init_2ch;
+ /* override the PCM rates, etc, as the codec doesn't give full list */
+ spec = codec->spec;
+ spec->pcm_playback.rates = SUPPORTED_RATES;
+ spec->pcm_playback.maxbps = SUPPORTED_MAXBPS;
+ spec->pcm_playback.formats = SUPPORTED_FORMATS;
+ spec->nv_dp_workaround = true;
+ return 0;
+}
+
+static int nvhdmi_7x_8ch_build_pcms(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int err = simple_playback_build_pcms(codec);
+ if (!err) {
+ struct hda_pcm *info = get_pcm_rec(spec, 0);
+ info->own_chmap = true;
+ }
+ return err;
+}
+
+static int nvhdmi_7x_8ch_build_controls(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ struct hda_pcm *info;
+ struct snd_pcm_chmap *chmap;
+ int err;
+
+ err = simple_playback_build_controls(codec);
+ if (err < 0)
+ return err;
+
+ /* add channel maps */
+ info = get_pcm_rec(spec, 0);
+ err = snd_pcm_add_chmap_ctls(info->pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ snd_pcm_alt_chmaps, 8, 0, &chmap);
+ if (err < 0)
+ return err;
+ switch (codec->preset->vendor_id) {
+ case 0x10de0002:
+ case 0x10de0003:
+ case 0x10de0005:
+ case 0x10de0006:
+ chmap->channel_mask = (1U << 2) | (1U << 8);
+ break;
+ case 0x10de0007:
+ chmap->channel_mask = (1U << 2) | (1U << 6) | (1U << 8);
+ }
+ return 0;
+}
+
+static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ int err = patch_nvhdmi_2ch(codec);
+ if (err < 0)
+ return err;
+ spec = codec->spec;
+ spec->multiout.max_channels = 8;
+ spec->pcm_playback = nvhdmi_pcm_playback_8ch_7x;
+ codec->patch_ops.init = nvhdmi_7x_init_8ch;
+ codec->patch_ops.build_pcms = nvhdmi_7x_8ch_build_pcms;
+ codec->patch_ops.build_controls = nvhdmi_7x_8ch_build_controls;
+
+ /* Initialize the audio infoframe channel mask and checksum to something
+ * valid */
+ nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8);
+
+ return 0;
+}
+
+/*
+ * NVIDIA codecs ignore ASP mapping for 2ch - confirmed on:
+ * - 0x10de0015
+ * - 0x10de0040
+ */
+static int nvhdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap,
+ struct hdac_cea_channel_speaker_allocation *cap, int channels)
+{
+ if (cap->ca_index == 0x00 && channels == 2)
+ return SNDRV_CTL_TLVT_CHMAP_FIXED;
+
+ /* If the speaker allocation matches the channel count, it is OK. */
+ if (cap->channels != channels)
+ return -1;
+
+ /* all channels are remappable freely */
+ return SNDRV_CTL_TLVT_CHMAP_VAR;
+}
+
+static int nvhdmi_chmap_validate(struct hdac_chmap *chmap,
+ int ca, int chs, unsigned char *map)
+{
+ if (ca == 0x00 && (map[0] != SNDRV_CHMAP_FL || map[1] != SNDRV_CHMAP_FR))
+ return -EINVAL;
+
+ return 0;
+}
+
+/* map from pin NID to port; port is 0-based */
+/* for Nvidia: assume widget NID starting from 4, with step 1 (4, 5, 6, ...) */
+static int nvhdmi_pin2port(void *audio_ptr, int pin_nid)
+{
+ return pin_nid - 4;
+}
+
+/* reverse-map from port to pin NID: see above */
+static int nvhdmi_port2pin(struct hda_codec *codec, int port)
+{
+ return port + 4;
+}
+
+static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = {
+ .pin2port = nvhdmi_pin2port,
+ .pin_eld_notify = generic_acomp_pin_eld_notify,
+ .master_bind = generic_acomp_master_bind,
+ .master_unbind = generic_acomp_master_unbind,
+};
+
+static int patch_nvhdmi(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ int err;
+
+ err = alloc_generic_hdmi(codec);
+ if (err < 0)
+ return err;
+ codec->dp_mst = true;
+
+ spec = codec->spec;
+ spec->dyn_pcm_assign = true;
+
+ err = hdmi_parse_codec(codec);
+ if (err < 0) {
+ generic_spec_free(codec);
+ return err;
+ }
+
+ generic_hdmi_init_per_pins(codec);
+
+ spec->dyn_pin_out = true;
+
+ spec->chmap.ops.chmap_cea_alloc_validate_get_type =
+ nvhdmi_chmap_cea_alloc_validate_get_type;
+ spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate;
+ spec->nv_dp_workaround = true;
+
+ codec->link_down_at_suspend = 1;
+
+ generic_acomp_init(codec, &nvhdmi_audio_ops, nvhdmi_port2pin);
+
+ return 0;
+}
+
+static int patch_nvhdmi_legacy(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ int err;
+
+ err = patch_generic_hdmi(codec);
+ if (err)
+ return err;
+
+ spec = codec->spec;
+ spec->dyn_pin_out = true;
+
+ spec->chmap.ops.chmap_cea_alloc_validate_get_type =
+ nvhdmi_chmap_cea_alloc_validate_get_type;
+ spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate;
+ spec->nv_dp_workaround = true;
+
+ codec->link_down_at_suspend = 1;
+
+ return 0;
+}
+
+/*
+ * The HDA codec on NVIDIA Tegra contains two scratch registers that are
+ * accessed using vendor-defined verbs. These registers can be used for
+ * interoperability between the HDA and HDMI drivers.
+ */
+
+/* Audio Function Group node */
+#define NVIDIA_AFG_NID 0x01
+
+/*
+ * The SCRATCH0 register is used to notify the HDMI codec of changes in audio
+ * format. On Tegra, bit 31 is used as a trigger that causes an interrupt to
+ * be raised in the HDMI codec. The remainder of the bits is arbitrary. This
+ * implementation stores the HDA format (see AC_FMT_*) in bits [15:0] and an
+ * additional bit (at position 30) to signal the validity of the format.
+ *
+ * | 31 | 30 | 29 16 | 15 0 |
+ * +---------+-------+--------+--------+
+ * | TRIGGER | VALID | UNUSED | FORMAT |
+ * +-----------------------------------|
+ *
+ * Note that for the trigger bit to take effect it needs to change value
+ * (i.e. it needs to be toggled).
+ */
+#define NVIDIA_GET_SCRATCH0 0xfa6
+#define NVIDIA_SET_SCRATCH0_BYTE0 0xfa7
+#define NVIDIA_SET_SCRATCH0_BYTE1 0xfa8
+#define NVIDIA_SET_SCRATCH0_BYTE2 0xfa9
+#define NVIDIA_SET_SCRATCH0_BYTE3 0xfaa
+#define NVIDIA_SCRATCH_TRIGGER (1 << 7)
+#define NVIDIA_SCRATCH_VALID (1 << 6)
+
+#define NVIDIA_GET_SCRATCH1 0xfab
+#define NVIDIA_SET_SCRATCH1_BYTE0 0xfac
+#define NVIDIA_SET_SCRATCH1_BYTE1 0xfad
+#define NVIDIA_SET_SCRATCH1_BYTE2 0xfae
+#define NVIDIA_SET_SCRATCH1_BYTE3 0xfaf
+
+/*
+ * The format parameter is the HDA audio format (see AC_FMT_*). If set to 0,
+ * the format is invalidated so that the HDMI codec can be disabled.
+ */
+static void tegra_hdmi_set_format(struct hda_codec *codec, unsigned int format)
+{
+ unsigned int value;
+
+ /* bits [31:30] contain the trigger and valid bits */
+ value = snd_hda_codec_read(codec, NVIDIA_AFG_NID, 0,
+ NVIDIA_GET_SCRATCH0, 0);
+ value = (value >> 24) & 0xff;
+
+ /* bits [15:0] are used to store the HDA format */
+ snd_hda_codec_write(codec, NVIDIA_AFG_NID, 0,
+ NVIDIA_SET_SCRATCH0_BYTE0,
+ (format >> 0) & 0xff);
+ snd_hda_codec_write(codec, NVIDIA_AFG_NID, 0,
+ NVIDIA_SET_SCRATCH0_BYTE1,
+ (format >> 8) & 0xff);
+
+ /* bits [16:24] are unused */
+ snd_hda_codec_write(codec, NVIDIA_AFG_NID, 0,
+ NVIDIA_SET_SCRATCH0_BYTE2, 0);
+
+ /*
+ * Bit 30 signals that the data is valid and hence that HDMI audio can
+ * be enabled.
+ */
+ if (format == 0)
+ value &= ~NVIDIA_SCRATCH_VALID;
+ else
+ value |= NVIDIA_SCRATCH_VALID;
+
+ /*
+ * Whenever the trigger bit is toggled, an interrupt is raised in the
+ * HDMI codec. The HDMI driver will use that as trigger to update its
+ * configuration.
+ */
+ value ^= NVIDIA_SCRATCH_TRIGGER;
+
+ snd_hda_codec_write(codec, NVIDIA_AFG_NID, 0,
+ NVIDIA_SET_SCRATCH0_BYTE3, value);
+}
+
+static int tegra_hdmi_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ int err;
+
+ err = generic_hdmi_playback_pcm_prepare(hinfo, codec, stream_tag,
+ format, substream);
+ if (err < 0)
+ return err;
+
+ /* notify the HDMI codec of the format change */
+ tegra_hdmi_set_format(codec, format);
+
+ return 0;
+}
+
+static int tegra_hdmi_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ /* invalidate the format in the HDMI codec */
+ tegra_hdmi_set_format(codec, 0);
+
+ return generic_hdmi_playback_pcm_cleanup(hinfo, codec, substream);
+}
+
+static struct hda_pcm *hda_find_pcm_by_type(struct hda_codec *codec, int type)
+{
+ struct hdmi_spec *spec = codec->spec;
+ unsigned int i;
+
+ for (i = 0; i < spec->num_pins; i++) {
+ struct hda_pcm *pcm = get_pcm_rec(spec, i);
+
+ if (pcm->pcm_type == type)
+ return pcm;
+ }
+
+ return NULL;
+}
+
+static int tegra_hdmi_build_pcms(struct hda_codec *codec)
+{
+ struct hda_pcm_stream *stream;
+ struct hda_pcm *pcm;
+ int err;
+
+ err = generic_hdmi_build_pcms(codec);
+ if (err < 0)
+ return err;
+
+ pcm = hda_find_pcm_by_type(codec, HDA_PCM_TYPE_HDMI);
+ if (!pcm)
+ return -ENODEV;
+
+ /*
+ * Override ->prepare() and ->cleanup() operations to notify the HDMI
+ * codec about format changes.
+ */
+ stream = &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK];
+ stream->ops.prepare = tegra_hdmi_pcm_prepare;
+ stream->ops.cleanup = tegra_hdmi_pcm_cleanup;
+
+ return 0;
+}
+
+static int patch_tegra_hdmi(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ int err;
+
+ err = patch_generic_hdmi(codec);
+ if (err)
+ return err;
+
+ codec->depop_delay = 10;
+ codec->patch_ops.build_pcms = tegra_hdmi_build_pcms;
+ spec = codec->spec;
+ spec->chmap.ops.chmap_cea_alloc_validate_get_type =
+ nvhdmi_chmap_cea_alloc_validate_get_type;
+ spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate;
+ spec->nv_dp_workaround = true;
+
+ return 0;
+}
+
+/*
+ * ATI/AMD-specific implementations
+ */
+
+#define is_amdhdmi_rev3_or_later(codec) \
+ ((codec)->core.vendor_id == 0x1002aa01 && \
+ ((codec)->core.revision_id & 0xff00) >= 0x0300)
+#define has_amd_full_remap_support(codec) is_amdhdmi_rev3_or_later(codec)
+
+/* ATI/AMD specific HDA pin verbs, see the AMD HDA Verbs specification */
+#define ATI_VERB_SET_CHANNEL_ALLOCATION 0x771
+#define ATI_VERB_SET_DOWNMIX_INFO 0x772
+#define ATI_VERB_SET_MULTICHANNEL_01 0x777
+#define ATI_VERB_SET_MULTICHANNEL_23 0x778
+#define ATI_VERB_SET_MULTICHANNEL_45 0x779
+#define ATI_VERB_SET_MULTICHANNEL_67 0x77a
+#define ATI_VERB_SET_HBR_CONTROL 0x77c
+#define ATI_VERB_SET_MULTICHANNEL_1 0x785
+#define ATI_VERB_SET_MULTICHANNEL_3 0x786
+#define ATI_VERB_SET_MULTICHANNEL_5 0x787
+#define ATI_VERB_SET_MULTICHANNEL_7 0x788
+#define ATI_VERB_SET_MULTICHANNEL_MODE 0x789
+#define ATI_VERB_GET_CHANNEL_ALLOCATION 0xf71
+#define ATI_VERB_GET_DOWNMIX_INFO 0xf72
+#define ATI_VERB_GET_MULTICHANNEL_01 0xf77
+#define ATI_VERB_GET_MULTICHANNEL_23 0xf78
+#define ATI_VERB_GET_MULTICHANNEL_45 0xf79
+#define ATI_VERB_GET_MULTICHANNEL_67 0xf7a
+#define ATI_VERB_GET_HBR_CONTROL 0xf7c
+#define ATI_VERB_GET_MULTICHANNEL_1 0xf85
+#define ATI_VERB_GET_MULTICHANNEL_3 0xf86
+#define ATI_VERB_GET_MULTICHANNEL_5 0xf87
+#define ATI_VERB_GET_MULTICHANNEL_7 0xf88
+#define ATI_VERB_GET_MULTICHANNEL_MODE 0xf89
+
+/* AMD specific HDA cvt verbs */
+#define ATI_VERB_SET_RAMP_RATE 0x770
+#define ATI_VERB_GET_RAMP_RATE 0xf70
+
+#define ATI_OUT_ENABLE 0x1
+
+#define ATI_MULTICHANNEL_MODE_PAIRED 0
+#define ATI_MULTICHANNEL_MODE_SINGLE 1
+
+#define ATI_HBR_CAPABLE 0x01
+#define ATI_HBR_ENABLE 0x10
+
+static int atihdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, unsigned char *buf, int *eld_size)
+{
+ WARN_ON(dev_id != 0);
+ /* call hda_eld.c ATI/AMD-specific function */
+ return snd_hdmi_get_eld_ati(codec, nid, buf, eld_size,
+ is_amdhdmi_rev3_or_later(codec));
+}
+
+static void atihdmi_pin_setup_infoframe(struct hda_codec *codec,
+ hda_nid_t pin_nid, int dev_id, int ca,
+ int active_channels, int conn_type)
+{
+ WARN_ON(dev_id != 0);
+ snd_hda_codec_write(codec, pin_nid, 0, ATI_VERB_SET_CHANNEL_ALLOCATION, ca);
+}
+
+static int atihdmi_paired_swap_fc_lfe(int pos)
+{
+ /*
+ * ATI/AMD have automatic FC/LFE swap built-in
+ * when in pairwise mapping mode.
+ */
+
+ switch (pos) {
+ /* see channel_allocations[].speakers[] */
+ case 2: return 3;
+ case 3: return 2;
+ default: break;
+ }
+
+ return pos;
+}
+
+static int atihdmi_paired_chmap_validate(struct hdac_chmap *chmap,
+ int ca, int chs, unsigned char *map)
+{
+ struct hdac_cea_channel_speaker_allocation *cap;
+ int i, j;
+
+ /* check that only channel pairs need to be remapped on old pre-rev3 ATI/AMD */
+
+ cap = snd_hdac_get_ch_alloc_from_ca(ca);
+ for (i = 0; i < chs; ++i) {
+ int mask = snd_hdac_chmap_to_spk_mask(map[i]);
+ bool ok = false;
+ bool companion_ok = false;
+
+ if (!mask)
+ continue;
+
+ for (j = 0 + i % 2; j < 8; j += 2) {
+ int chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j);
+ if (cap->speakers[chan_idx] == mask) {
+ /* channel is in a supported position */
+ ok = true;
+
+ if (i % 2 == 0 && i + 1 < chs) {
+ /* even channel, check the odd companion */
+ int comp_chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j + 1);
+ int comp_mask_req = snd_hdac_chmap_to_spk_mask(map[i+1]);
+ int comp_mask_act = cap->speakers[comp_chan_idx];
+
+ if (comp_mask_req == comp_mask_act)
+ companion_ok = true;
+ else
+ return -EINVAL;
+ }
+ break;
+ }
+ }
+
+ if (!ok)
+ return -EINVAL;
+
+ if (companion_ok)
+ i++; /* companion channel already checked */
+ }
+
+ return 0;
+}
+
+static int atihdmi_pin_set_slot_channel(struct hdac_device *hdac,
+ hda_nid_t pin_nid, int hdmi_slot, int stream_channel)
+{
+ struct hda_codec *codec = hdac_to_hda_codec(hdac);
+ int verb;
+ int ati_channel_setup = 0;
+
+ if (hdmi_slot > 7)
+ return -EINVAL;
+
+ if (!has_amd_full_remap_support(codec)) {
+ hdmi_slot = atihdmi_paired_swap_fc_lfe(hdmi_slot);
+
+ /* In case this is an odd slot but without stream channel, do not
+ * disable the slot since the corresponding even slot could have a
+ * channel. In case neither have a channel, the slot pair will be
+ * disabled when this function is called for the even slot. */
+ if (hdmi_slot % 2 != 0 && stream_channel == 0xf)
+ return 0;
+
+ hdmi_slot -= hdmi_slot % 2;
+
+ if (stream_channel != 0xf)
+ stream_channel -= stream_channel % 2;
+ }
+
+ verb = ATI_VERB_SET_MULTICHANNEL_01 + hdmi_slot/2 + (hdmi_slot % 2) * 0x00e;
+
+ /* ati_channel_setup format: [7..4] = stream_channel_id, [1] = mute, [0] = enable */
+
+ if (stream_channel != 0xf)
+ ati_channel_setup = (stream_channel << 4) | ATI_OUT_ENABLE;
+
+ return snd_hda_codec_write(codec, pin_nid, 0, verb, ati_channel_setup);
+}
+
+static int atihdmi_pin_get_slot_channel(struct hdac_device *hdac,
+ hda_nid_t pin_nid, int asp_slot)
+{
+ struct hda_codec *codec = hdac_to_hda_codec(hdac);
+ bool was_odd = false;
+ int ati_asp_slot = asp_slot;
+ int verb;
+ int ati_channel_setup;
+
+ if (asp_slot > 7)
+ return -EINVAL;
+
+ if (!has_amd_full_remap_support(codec)) {
+ ati_asp_slot = atihdmi_paired_swap_fc_lfe(asp_slot);
+ if (ati_asp_slot % 2 != 0) {
+ ati_asp_slot -= 1;
+ was_odd = true;
+ }
+ }
+
+ verb = ATI_VERB_GET_MULTICHANNEL_01 + ati_asp_slot/2 + (ati_asp_slot % 2) * 0x00e;
+
+ ati_channel_setup = snd_hda_codec_read(codec, pin_nid, 0, verb, 0);
+
+ if (!(ati_channel_setup & ATI_OUT_ENABLE))
+ return 0xf;
+
+ return ((ati_channel_setup & 0xf0) >> 4) + !!was_odd;
+}
+
+static int atihdmi_paired_chmap_cea_alloc_validate_get_type(
+ struct hdac_chmap *chmap,
+ struct hdac_cea_channel_speaker_allocation *cap,
+ int channels)
+{
+ int c;
+
+ /*
+ * Pre-rev3 ATI/AMD codecs operate in a paired channel mode, so
+ * we need to take that into account (a single channel may take 2
+ * channel slots if we need to carry a silent channel next to it).
+ * On Rev3+ AMD codecs this function is not used.
+ */
+ int chanpairs = 0;
+
+ /* We only produce even-numbered channel count TLVs */
+ if ((channels % 2) != 0)
+ return -1;
+
+ for (c = 0; c < 7; c += 2) {
+ if (cap->speakers[c] || cap->speakers[c+1])
+ chanpairs++;
+ }
+
+ if (chanpairs * 2 != channels)
+ return -1;
+
+ return SNDRV_CTL_TLVT_CHMAP_PAIRED;
+}
+
+static void atihdmi_paired_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap,
+ struct hdac_cea_channel_speaker_allocation *cap,
+ unsigned int *chmap, int channels)
+{
+ /* produce paired maps for pre-rev3 ATI/AMD codecs */
+ int count = 0;
+ int c;
+
+ for (c = 7; c >= 0; c--) {
+ int chan = 7 - atihdmi_paired_swap_fc_lfe(7 - c);
+ int spk = cap->speakers[chan];
+ if (!spk) {
+ /* add N/A channel if the companion channel is occupied */
+ if (cap->speakers[chan + (chan % 2 ? -1 : 1)])
+ chmap[count++] = SNDRV_CHMAP_NA;
+
+ continue;
+ }
+
+ chmap[count++] = snd_hdac_spk_to_chmap(spk);
+ }
+
+ WARN_ON(count != channels);
+}
+
+static int atihdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
+ int dev_id, bool hbr)
+{
+ int hbr_ctl, hbr_ctl_new;
+
+ WARN_ON(dev_id != 0);
+
+ hbr_ctl = snd_hda_codec_read(codec, pin_nid, 0, ATI_VERB_GET_HBR_CONTROL, 0);
+ if (hbr_ctl >= 0 && (hbr_ctl & ATI_HBR_CAPABLE)) {
+ if (hbr)
+ hbr_ctl_new = hbr_ctl | ATI_HBR_ENABLE;
+ else
+ hbr_ctl_new = hbr_ctl & ~ATI_HBR_ENABLE;
+
+ codec_dbg(codec,
+ "atihdmi_pin_hbr_setup: NID=0x%x, %shbr-ctl=0x%x\n",
+ pin_nid,
+ hbr_ctl == hbr_ctl_new ? "" : "new-",
+ hbr_ctl_new);
+
+ if (hbr_ctl != hbr_ctl_new)
+ snd_hda_codec_write(codec, pin_nid, 0,
+ ATI_VERB_SET_HBR_CONTROL,
+ hbr_ctl_new);
+
+ } else if (hbr)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int atihdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
+ hda_nid_t pin_nid, int dev_id,
+ u32 stream_tag, int format)
+{
+ if (is_amdhdmi_rev3_or_later(codec)) {
+ int ramp_rate = 180; /* default as per AMD spec */
+ /* disable ramp-up/down for non-pcm as per AMD spec */
+ if (format & AC_FMT_TYPE_NON_PCM)
+ ramp_rate = 0;
+
+ snd_hda_codec_write(codec, cvt_nid, 0, ATI_VERB_SET_RAMP_RATE, ramp_rate);
+ }
+
+ return hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id,
+ stream_tag, format);
+}
+
+
+static int atihdmi_init(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx, err;
+
+ err = generic_hdmi_init(codec);
+
+ if (err)
+ return err;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+
+ /* make sure downmix information in infoframe is zero */
+ snd_hda_codec_write(codec, per_pin->pin_nid, 0, ATI_VERB_SET_DOWNMIX_INFO, 0);
+
+ /* enable channel-wise remap mode if supported */
+ if (has_amd_full_remap_support(codec))
+ snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+ ATI_VERB_SET_MULTICHANNEL_MODE,
+ ATI_MULTICHANNEL_MODE_SINGLE);
+ }
+ codec->auto_runtime_pm = 1;
+
+ return 0;
+}
+
+/* map from pin NID to port; port is 0-based */
+/* for AMD: assume widget NID starting from 3, with step 2 (3, 5, 7, ...) */
+static int atihdmi_pin2port(void *audio_ptr, int pin_nid)
+{
+ return pin_nid / 2 - 1;
+}
+
+/* reverse-map from port to pin NID: see above */
+static int atihdmi_port2pin(struct hda_codec *codec, int port)
+{
+ return port * 2 + 3;
+}
+
+static const struct drm_audio_component_audio_ops atihdmi_audio_ops = {
+ .pin2port = atihdmi_pin2port,
+ .pin_eld_notify = generic_acomp_pin_eld_notify,
+ .master_bind = generic_acomp_master_bind,
+ .master_unbind = generic_acomp_master_unbind,
+};
+
+static int patch_atihdmi(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec;
+ struct hdmi_spec_per_cvt *per_cvt;
+ int err, cvt_idx;
+
+ err = patch_generic_hdmi(codec);
+
+ if (err)
+ return err;
+
+ codec->patch_ops.init = atihdmi_init;
+
+ spec = codec->spec;
+
+ spec->ops.pin_get_eld = atihdmi_pin_get_eld;
+ spec->ops.pin_setup_infoframe = atihdmi_pin_setup_infoframe;
+ spec->ops.pin_hbr_setup = atihdmi_pin_hbr_setup;
+ spec->ops.setup_stream = atihdmi_setup_stream;
+
+ spec->chmap.ops.pin_get_slot_channel = atihdmi_pin_get_slot_channel;
+ spec->chmap.ops.pin_set_slot_channel = atihdmi_pin_set_slot_channel;
+
+ if (!has_amd_full_remap_support(codec)) {
+ /* override to ATI/AMD-specific versions with pairwise mapping */
+ spec->chmap.ops.chmap_cea_alloc_validate_get_type =
+ atihdmi_paired_chmap_cea_alloc_validate_get_type;
+ spec->chmap.ops.cea_alloc_to_tlv_chmap =
+ atihdmi_paired_cea_alloc_to_tlv_chmap;
+ spec->chmap.ops.chmap_validate = atihdmi_paired_chmap_validate;
+ }
+
+ /* ATI/AMD converters do not advertise all of their capabilities */
+ for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+ per_cvt = get_cvt(spec, cvt_idx);
+ per_cvt->channels_max = max(per_cvt->channels_max, 8u);
+ per_cvt->rates |= SUPPORTED_RATES;
+ per_cvt->formats |= SUPPORTED_FORMATS;
+ per_cvt->maxbps = max(per_cvt->maxbps, 24u);
+ }
+
+ spec->chmap.channels_max = max(spec->chmap.channels_max, 8u);
+
+ /* AMD GPUs have neither EPSS nor CLKSTOP bits, hence preventing
+ * the link-down as is. Tell the core to allow it.
+ */
+ codec->link_down_at_suspend = 1;
+
+ generic_acomp_init(codec, &atihdmi_audio_ops, atihdmi_port2pin);
+
+ return 0;
+}
+
+/* VIA HDMI Implementation */
+#define VIAHDMI_CVT_NID 0x02 /* audio converter1 */
+#define VIAHDMI_PIN_NID 0x03 /* HDMI output pin1 */
+
+static int patch_via_hdmi(struct hda_codec *codec)
+{
+ return patch_simple_hdmi(codec, VIAHDMI_CVT_NID, VIAHDMI_PIN_NID);
+}
+
+static int patch_gf_hdmi(struct hda_codec *codec)
+{
+ int err;
+
+ err = patch_generic_hdmi(codec);
+ if (err)
+ return err;
+
+ /*
+ * Glenfly GPUs have two codecs, stream switches from one codec to
+ * another, need to do actual clean-ups in codec_cleanup_stream
+ */
+ codec->no_sticky_stream = 1;
+ return 0;
+}
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_hdmi[] = {
+HDA_CODEC_ENTRY(0x1002793c, "RS600 HDMI", patch_atihdmi),
+HDA_CODEC_ENTRY(0x10027919, "RS600 HDMI", patch_atihdmi),
+HDA_CODEC_ENTRY(0x1002791a, "RS690/780 HDMI", patch_atihdmi),
+HDA_CODEC_ENTRY(0x1002aa01, "R6xx HDMI", patch_atihdmi),
+HDA_CODEC_ENTRY(0x10951390, "SiI1390 HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x10951392, "SiI1392 HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x17e80047, "Chrontel HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x10de0001, "MCP73 HDMI", patch_nvhdmi_2ch),
+HDA_CODEC_ENTRY(0x10de0002, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x),
+HDA_CODEC_ENTRY(0x10de0003, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x),
+HDA_CODEC_ENTRY(0x10de0004, "GPU 04 HDMI", patch_nvhdmi_8ch_7x),
+HDA_CODEC_ENTRY(0x10de0005, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x),
+HDA_CODEC_ENTRY(0x10de0006, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x),
+HDA_CODEC_ENTRY(0x10de0007, "MCP79/7A HDMI", patch_nvhdmi_8ch_7x),
+HDA_CODEC_ENTRY(0x10de0008, "GPU 08 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0009, "GPU 09 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de000a, "GPU 0a HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de000b, "GPU 0b HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de000c, "MCP89 HDMI", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de000d, "GPU 0d HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0010, "GPU 10 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0011, "GPU 11 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0012, "GPU 12 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0013, "GPU 13 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0014, "GPU 14 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0015, "GPU 15 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0016, "GPU 16 HDMI/DP", patch_nvhdmi_legacy),
+/* 17 is known to be absent */
+HDA_CODEC_ENTRY(0x10de0018, "GPU 18 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0019, "GPU 19 HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de001a, "GPU 1a HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de001b, "GPU 1b HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de001c, "GPU 1c HDMI/DP", patch_nvhdmi_legacy),
+HDA_CODEC_ENTRY(0x10de0020, "Tegra30 HDMI", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de0022, "Tegra114 HDMI", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de0028, "Tegra124 HDMI", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de0029, "Tegra210 HDMI/DP", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de002d, "Tegra186 HDMI/DP0", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de002e, "Tegra186 HDMI/DP1", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de002f, "Tegra194 HDMI/DP2", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de0030, "Tegra194 HDMI/DP3", patch_tegra_hdmi),
+HDA_CODEC_ENTRY(0x10de0040, "GPU 40 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0041, "GPU 41 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0042, "GPU 42 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0043, "GPU 43 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0044, "GPU 44 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0045, "GPU 45 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0050, "GPU 50 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0051, "GPU 51 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0052, "GPU 52 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0060, "GPU 60 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0061, "GPU 61 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0062, "GPU 62 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0067, "MCP67 HDMI", patch_nvhdmi_2ch),
+HDA_CODEC_ENTRY(0x10de0070, "GPU 70 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0071, "GPU 71 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0072, "GPU 72 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0073, "GPU 73 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0074, "GPU 74 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0076, "GPU 76 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de007b, "GPU 7b HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de007c, "GPU 7c HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de007d, "GPU 7d HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de007e, "GPU 7e HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0080, "GPU 80 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0081, "GPU 81 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0082, "GPU 82 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0083, "GPU 83 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0084, "GPU 84 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0090, "GPU 90 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0091, "GPU 91 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0092, "GPU 92 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0093, "GPU 93 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0094, "GPU 94 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0095, "GPU 95 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0097, "GPU 97 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0098, "GPU 98 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de0099, "GPU 99 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de009a, "GPU 9a HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de009d, "GPU 9d HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de009e, "GPU 9e HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de009f, "GPU 9f HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de00a0, "GPU a0 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de00a3, "GPU a3 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de00a4, "GPU a4 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de00a5, "GPU a5 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de00a6, "GPU a6 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de00a7, "GPU a7 HDMI/DP", patch_nvhdmi),
+HDA_CODEC_ENTRY(0x10de8001, "MCP73 HDMI", patch_nvhdmi_2ch),
+HDA_CODEC_ENTRY(0x10de8067, "MCP67/68 HDMI", patch_nvhdmi_2ch),
+HDA_CODEC_ENTRY(0x67663d82, "Arise 82 HDMI/DP", patch_gf_hdmi),
+HDA_CODEC_ENTRY(0x67663d83, "Arise 83 HDMI/DP", patch_gf_hdmi),
+HDA_CODEC_ENTRY(0x67663d84, "Arise 84 HDMI/DP", patch_gf_hdmi),
+HDA_CODEC_ENTRY(0x67663d85, "Arise 85 HDMI/DP", patch_gf_hdmi),
+HDA_CODEC_ENTRY(0x67663d86, "Arise 86 HDMI/DP", patch_gf_hdmi),
+HDA_CODEC_ENTRY(0x67663d87, "Arise 87 HDMI/DP", patch_gf_hdmi),
+HDA_CODEC_ENTRY(0x11069f80, "VX900 HDMI/DP", patch_via_hdmi),
+HDA_CODEC_ENTRY(0x11069f81, "VX900 HDMI/DP", patch_via_hdmi),
+HDA_CODEC_ENTRY(0x11069f84, "VX11 HDMI/DP", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x11069f85, "VX11 HDMI/DP", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x80860054, "IbexPeak HDMI", patch_i915_cpt_hdmi),
+HDA_CODEC_ENTRY(0x80862800, "Geminilake HDMI", patch_i915_glk_hdmi),
+HDA_CODEC_ENTRY(0x80862801, "Bearlake HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x80862802, "Cantiga HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x80862803, "Eaglelake HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x80862804, "IbexPeak HDMI", patch_i915_cpt_hdmi),
+HDA_CODEC_ENTRY(0x80862805, "CougarPoint HDMI", patch_i915_cpt_hdmi),
+HDA_CODEC_ENTRY(0x80862806, "PantherPoint HDMI", patch_i915_cpt_hdmi),
+HDA_CODEC_ENTRY(0x80862807, "Haswell HDMI", patch_i915_hsw_hdmi),
+HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi),
+HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi),
+HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi),
+HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi),
+HDA_CODEC_ENTRY(0x8086280c, "Cannonlake HDMI", patch_i915_glk_hdmi),
+HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi),
+HDA_CODEC_ENTRY(0x8086280f, "Icelake HDMI", patch_i915_icl_hdmi),
+HDA_CODEC_ENTRY(0x80862812, "Tigerlake HDMI", patch_i915_tgl_hdmi),
+HDA_CODEC_ENTRY(0x80862814, "DG1 HDMI", patch_i915_tgl_hdmi),
+HDA_CODEC_ENTRY(0x80862815, "Alderlake HDMI", patch_i915_tgl_hdmi),
+HDA_CODEC_ENTRY(0x80862816, "Rocketlake HDMI", patch_i915_tgl_hdmi),
+HDA_CODEC_ENTRY(0x80862819, "DG2 HDMI", patch_i915_tgl_hdmi),
+HDA_CODEC_ENTRY(0x8086281a, "Jasperlake HDMI", patch_i915_icl_hdmi),
+HDA_CODEC_ENTRY(0x8086281b, "Elkhartlake HDMI", patch_i915_icl_hdmi),
+HDA_CODEC_ENTRY(0x8086281c, "Alderlake-P HDMI", patch_i915_tgl_hdmi),
+HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi),
+HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi),
+HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi),
+HDA_CODEC_ENTRY(0x808629fb, "Crestline HDMI", patch_generic_hdmi),
+/* special ID for generic HDMI */
+HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC_HDMI, "Generic HDMI", patch_generic_hdmi),
+{} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_hdmi);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HDMI HD-audio codec");
+MODULE_ALIAS("snd-hda-codec-intelhdmi");
+MODULE_ALIAS("snd-hda-codec-nvhdmi");
+MODULE_ALIAS("snd-hda-codec-atihdmi");
+
+static struct hda_codec_driver hdmi_driver = {
+ .id = snd_hda_id_hdmi,
+};
+
+module_hda_codec_driver(hdmi_driver);
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
new file mode 100644
index 000000000..412fbe098
--- /dev/null
+++ b/sound/pci/hda/patch_realtek.c
@@ -0,0 +1,11613 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for Realtek ALC codecs
+ *
+ * Copyright (c) 2004 Kailang Yang <kailang@realtek.com.tw>
+ * PeiSen Hou <pshou@realtek.com.tw>
+ * Takashi Iwai <tiwai@suse.de>
+ * Jonathan Woithe <jwoithe@just42.net>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+/* keep halting ALC5505 DSP, for power saving */
+#define HALT_REALTEK_ALC5505
+
+/* extra amp-initialization sequence types */
+enum {
+ ALC_INIT_UNDEFINED,
+ ALC_INIT_NONE,
+ ALC_INIT_DEFAULT,
+};
+
+enum {
+ ALC_HEADSET_MODE_UNKNOWN,
+ ALC_HEADSET_MODE_UNPLUGGED,
+ ALC_HEADSET_MODE_HEADSET,
+ ALC_HEADSET_MODE_MIC,
+ ALC_HEADSET_MODE_HEADPHONE,
+};
+
+enum {
+ ALC_HEADSET_TYPE_UNKNOWN,
+ ALC_HEADSET_TYPE_CTIA,
+ ALC_HEADSET_TYPE_OMTP,
+};
+
+enum {
+ ALC_KEY_MICMUTE_INDEX,
+};
+
+struct alc_customize_define {
+ unsigned int sku_cfg;
+ unsigned char port_connectivity;
+ unsigned char check_sum;
+ unsigned char customization;
+ unsigned char external_amp;
+ unsigned int enable_pcbeep:1;
+ unsigned int platform_type:1;
+ unsigned int swap:1;
+ unsigned int override:1;
+ unsigned int fixup:1; /* Means that this sku is set by driver, not read from hw */
+};
+
+struct alc_coef_led {
+ unsigned int idx;
+ unsigned int mask;
+ unsigned int on;
+ unsigned int off;
+};
+
+struct alc_spec {
+ struct hda_gen_spec gen; /* must be at head */
+
+ /* codec parameterization */
+ struct alc_customize_define cdefine;
+ unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */
+
+ /* GPIO bits */
+ unsigned int gpio_mask;
+ unsigned int gpio_dir;
+ unsigned int gpio_data;
+ bool gpio_write_delay; /* add a delay before writing gpio_data */
+
+ /* mute LED for HP laptops, see vref_mute_led_set() */
+ int mute_led_polarity;
+ int micmute_led_polarity;
+ hda_nid_t mute_led_nid;
+ hda_nid_t cap_mute_led_nid;
+
+ unsigned int gpio_mute_led_mask;
+ unsigned int gpio_mic_led_mask;
+ struct alc_coef_led mute_led_coef;
+ struct alc_coef_led mic_led_coef;
+ struct mutex coef_mutex;
+
+ hda_nid_t headset_mic_pin;
+ hda_nid_t headphone_mic_pin;
+ int current_headset_mode;
+ int current_headset_type;
+
+ /* hooks */
+ void (*init_hook)(struct hda_codec *codec);
+#ifdef CONFIG_PM
+ void (*power_hook)(struct hda_codec *codec);
+#endif
+ void (*shutup)(struct hda_codec *codec);
+ void (*reboot_notify)(struct hda_codec *codec);
+
+ int init_amp;
+ int codec_variant; /* flag for other variants */
+ unsigned int has_alc5505_dsp:1;
+ unsigned int no_depop_delay:1;
+ unsigned int done_hp_init:1;
+ unsigned int no_shutup_pins:1;
+ unsigned int ultra_low_power:1;
+ unsigned int has_hs_key:1;
+ unsigned int no_internal_mic_pin:1;
+ unsigned int en_3kpull_low:1;
+
+ /* for PLL fix */
+ hda_nid_t pll_nid;
+ unsigned int pll_coef_idx, pll_coef_bit;
+ unsigned int coef0;
+ struct input_dev *kb_dev;
+ u8 alc_mute_keycode_map[1];
+};
+
+/*
+ * COEF access helper functions
+ */
+
+static void coef_mutex_lock(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ snd_hda_power_up_pm(codec);
+ mutex_lock(&spec->coef_mutex);
+}
+
+static void coef_mutex_unlock(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ mutex_unlock(&spec->coef_mutex);
+ snd_hda_power_down_pm(codec);
+}
+
+static int __alc_read_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx)
+{
+ unsigned int val;
+
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, coef_idx);
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF, 0);
+ return val;
+}
+
+static int alc_read_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx)
+{
+ unsigned int val;
+
+ coef_mutex_lock(codec);
+ val = __alc_read_coefex_idx(codec, nid, coef_idx);
+ coef_mutex_unlock(codec);
+ return val;
+}
+
+#define alc_read_coef_idx(codec, coef_idx) \
+ alc_read_coefex_idx(codec, 0x20, coef_idx)
+
+static void __alc_write_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx, unsigned int coef_val)
+{
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, coef_idx);
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PROC_COEF, coef_val);
+}
+
+static void alc_write_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx, unsigned int coef_val)
+{
+ coef_mutex_lock(codec);
+ __alc_write_coefex_idx(codec, nid, coef_idx, coef_val);
+ coef_mutex_unlock(codec);
+}
+
+#define alc_write_coef_idx(codec, coef_idx, coef_val) \
+ alc_write_coefex_idx(codec, 0x20, coef_idx, coef_val)
+
+static void __alc_update_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx, unsigned int mask,
+ unsigned int bits_set)
+{
+ unsigned int val = __alc_read_coefex_idx(codec, nid, coef_idx);
+
+ if (val != -1)
+ __alc_write_coefex_idx(codec, nid, coef_idx,
+ (val & ~mask) | bits_set);
+}
+
+static void alc_update_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx, unsigned int mask,
+ unsigned int bits_set)
+{
+ coef_mutex_lock(codec);
+ __alc_update_coefex_idx(codec, nid, coef_idx, mask, bits_set);
+ coef_mutex_unlock(codec);
+}
+
+#define alc_update_coef_idx(codec, coef_idx, mask, bits_set) \
+ alc_update_coefex_idx(codec, 0x20, coef_idx, mask, bits_set)
+
+/* a special bypass for COEF 0; read the cached value at the second time */
+static unsigned int alc_get_coef0(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (!spec->coef0)
+ spec->coef0 = alc_read_coef_idx(codec, 0);
+ return spec->coef0;
+}
+
+/* coef writes/updates batch */
+struct coef_fw {
+ unsigned char nid;
+ unsigned char idx;
+ unsigned short mask;
+ unsigned short val;
+};
+
+#define UPDATE_COEFEX(_nid, _idx, _mask, _val) \
+ { .nid = (_nid), .idx = (_idx), .mask = (_mask), .val = (_val) }
+#define WRITE_COEFEX(_nid, _idx, _val) UPDATE_COEFEX(_nid, _idx, -1, _val)
+#define WRITE_COEF(_idx, _val) WRITE_COEFEX(0x20, _idx, _val)
+#define UPDATE_COEF(_idx, _mask, _val) UPDATE_COEFEX(0x20, _idx, _mask, _val)
+
+static void alc_process_coef_fw(struct hda_codec *codec,
+ const struct coef_fw *fw)
+{
+ coef_mutex_lock(codec);
+ for (; fw->nid; fw++) {
+ if (fw->mask == (unsigned short)-1)
+ __alc_write_coefex_idx(codec, fw->nid, fw->idx, fw->val);
+ else
+ __alc_update_coefex_idx(codec, fw->nid, fw->idx,
+ fw->mask, fw->val);
+ }
+ coef_mutex_unlock(codec);
+}
+
+/*
+ * GPIO setup tables, used in initialization
+ */
+
+/* Enable GPIO mask and set output */
+static void alc_setup_gpio(struct hda_codec *codec, unsigned int mask)
+{
+ struct alc_spec *spec = codec->spec;
+
+ spec->gpio_mask |= mask;
+ spec->gpio_dir |= mask;
+ spec->gpio_data |= mask;
+}
+
+static void alc_write_gpio_data(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+ spec->gpio_data);
+}
+
+static void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask,
+ bool on)
+{
+ struct alc_spec *spec = codec->spec;
+ unsigned int oldval = spec->gpio_data;
+
+ if (on)
+ spec->gpio_data |= mask;
+ else
+ spec->gpio_data &= ~mask;
+ if (oldval != spec->gpio_data)
+ alc_write_gpio_data(codec);
+}
+
+static void alc_write_gpio(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (!spec->gpio_mask)
+ return;
+
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_MASK, spec->gpio_mask);
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir);
+ if (spec->gpio_write_delay)
+ msleep(1);
+ alc_write_gpio_data(codec);
+}
+
+static void alc_fixup_gpio(struct hda_codec *codec, int action,
+ unsigned int mask)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ alc_setup_gpio(codec, mask);
+}
+
+static void alc_fixup_gpio1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_gpio(codec, action, 0x01);
+}
+
+static void alc_fixup_gpio2(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_gpio(codec, action, 0x02);
+}
+
+static void alc_fixup_gpio3(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_gpio(codec, action, 0x03);
+}
+
+static void alc_fixup_gpio4(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_gpio(codec, action, 0x04);
+}
+
+static void alc_fixup_micmute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PROBE)
+ snd_hda_gen_add_micmute_led_cdev(codec, NULL);
+}
+
+/*
+ * Fix hardware PLL issue
+ * On some codecs, the analog PLL gating control must be off while
+ * the default value is 1.
+ */
+static void alc_fix_pll(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (spec->pll_nid)
+ alc_update_coefex_idx(codec, spec->pll_nid, spec->pll_coef_idx,
+ 1 << spec->pll_coef_bit, 0);
+}
+
+static void alc_fix_pll_init(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int coef_idx, unsigned int coef_bit)
+{
+ struct alc_spec *spec = codec->spec;
+ spec->pll_nid = nid;
+ spec->pll_coef_idx = coef_idx;
+ spec->pll_coef_bit = coef_bit;
+ alc_fix_pll(codec);
+}
+
+/* update the master volume per volume-knob's unsol event */
+static void alc_update_knob_master(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ unsigned int val;
+ struct snd_kcontrol *kctl;
+ struct snd_ctl_elem_value *uctl;
+
+ kctl = snd_hda_find_mixer_ctl(codec, "Master Playback Volume");
+ if (!kctl)
+ return;
+ uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+ if (!uctl)
+ return;
+ val = snd_hda_codec_read(codec, jack->nid, 0,
+ AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
+ val &= HDA_AMP_VOLMASK;
+ uctl->value.integer.value[0] = val;
+ uctl->value.integer.value[1] = val;
+ kctl->put(kctl, uctl);
+ kfree(uctl);
+}
+
+static void alc880_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+ /* For some reason, the res given from ALC880 is broken.
+ Here we adjust it properly. */
+ snd_hda_jack_unsol_event(codec, res >> 2);
+}
+
+/* Change EAPD to verb control */
+static void alc_fill_eapd_coef(struct hda_codec *codec)
+{
+ int coef;
+
+ coef = alc_get_coef0(codec);
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0262:
+ alc_update_coef_idx(codec, 0x7, 0, 1<<5);
+ break;
+ case 0x10ec0267:
+ case 0x10ec0268:
+ alc_update_coef_idx(codec, 0x7, 0, 1<<13);
+ break;
+ case 0x10ec0269:
+ if ((coef & 0x00f0) == 0x0010)
+ alc_update_coef_idx(codec, 0xd, 0, 1<<14);
+ if ((coef & 0x00f0) == 0x0020)
+ alc_update_coef_idx(codec, 0x4, 1<<15, 0);
+ if ((coef & 0x00f0) == 0x0030)
+ alc_update_coef_idx(codec, 0x10, 1<<9, 0);
+ break;
+ case 0x10ec0280:
+ case 0x10ec0284:
+ case 0x10ec0290:
+ case 0x10ec0292:
+ alc_update_coef_idx(codec, 0x4, 1<<15, 0);
+ break;
+ case 0x10ec0225:
+ case 0x10ec0295:
+ case 0x10ec0299:
+ alc_update_coef_idx(codec, 0x67, 0xf000, 0x3000);
+ fallthrough;
+ case 0x10ec0215:
+ case 0x10ec0230:
+ case 0x10ec0233:
+ case 0x10ec0235:
+ case 0x10ec0236:
+ case 0x10ec0245:
+ case 0x10ec0255:
+ case 0x10ec0256:
+ case 0x19e58326:
+ case 0x10ec0257:
+ case 0x10ec0282:
+ case 0x10ec0283:
+ case 0x10ec0286:
+ case 0x10ec0288:
+ case 0x10ec0285:
+ case 0x10ec0298:
+ case 0x10ec0289:
+ case 0x10ec0300:
+ alc_update_coef_idx(codec, 0x10, 1<<9, 0);
+ break;
+ case 0x10ec0275:
+ alc_update_coef_idx(codec, 0xe, 0, 1<<0);
+ break;
+ case 0x10ec0287:
+ alc_update_coef_idx(codec, 0x10, 1<<9, 0);
+ alc_write_coef_idx(codec, 0x8, 0x4ab7);
+ break;
+ case 0x10ec0293:
+ alc_update_coef_idx(codec, 0xa, 1<<13, 0);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ case 0x10ec0700:
+ case 0x10ec0701:
+ case 0x10ec0703:
+ case 0x10ec0711:
+ alc_update_coef_idx(codec, 0x10, 1<<15, 0);
+ break;
+ case 0x10ec0662:
+ if ((coef & 0x00f0) == 0x0030)
+ alc_update_coef_idx(codec, 0x4, 1<<10, 0); /* EAPD Ctrl */
+ break;
+ case 0x10ec0272:
+ case 0x10ec0273:
+ case 0x10ec0663:
+ case 0x10ec0665:
+ case 0x10ec0670:
+ case 0x10ec0671:
+ case 0x10ec0672:
+ alc_update_coef_idx(codec, 0xd, 0, 1<<14); /* EAPD Ctrl */
+ break;
+ case 0x10ec0222:
+ case 0x10ec0623:
+ alc_update_coef_idx(codec, 0x19, 1<<13, 0);
+ break;
+ case 0x10ec0668:
+ alc_update_coef_idx(codec, 0x7, 3<<13, 0);
+ break;
+ case 0x10ec0867:
+ alc_update_coef_idx(codec, 0x4, 1<<10, 0);
+ break;
+ case 0x10ec0888:
+ if ((coef & 0x00f0) == 0x0020 || (coef & 0x00f0) == 0x0030)
+ alc_update_coef_idx(codec, 0x7, 1<<5, 0);
+ break;
+ case 0x10ec0892:
+ case 0x10ec0897:
+ alc_update_coef_idx(codec, 0x7, 1<<5, 0);
+ break;
+ case 0x10ec0899:
+ case 0x10ec0900:
+ case 0x10ec0b00:
+ case 0x10ec1168:
+ case 0x10ec1220:
+ alc_update_coef_idx(codec, 0x7, 1<<1, 0);
+ break;
+ }
+}
+
+/* additional initialization for ALC888 variants */
+static void alc888_coef_init(struct hda_codec *codec)
+{
+ switch (alc_get_coef0(codec) & 0x00f0) {
+ /* alc888-VA */
+ case 0x00:
+ /* alc888-VB */
+ case 0x10:
+ alc_update_coef_idx(codec, 7, 0, 0x2030); /* Turn EAPD to High */
+ break;
+ }
+}
+
+/* turn on/off EAPD control (only if available) */
+static void set_eapd(struct hda_codec *codec, hda_nid_t nid, int on)
+{
+ if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+ return;
+ if (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE,
+ on ? 2 : 0);
+}
+
+/* turn on/off EAPD controls of the codec */
+static void alc_auto_setup_eapd(struct hda_codec *codec, bool on)
+{
+ /* We currently only handle front, HP */
+ static const hda_nid_t pins[] = {
+ 0x0f, 0x10, 0x14, 0x15, 0x17, 0
+ };
+ const hda_nid_t *p;
+ for (p = pins; *p; p++)
+ set_eapd(codec, *p, on);
+}
+
+static int find_ext_mic_pin(struct hda_codec *codec);
+
+static void alc_headset_mic_no_shutup(struct hda_codec *codec)
+{
+ const struct hda_pincfg *pin;
+ int mic_pin = find_ext_mic_pin(codec);
+ int i;
+
+ /* don't shut up pins when unloading the driver; otherwise it breaks
+ * the default pin setup at the next load of the driver
+ */
+ if (codec->bus->shutdown)
+ return;
+
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ /* use read here for syncing after issuing each verb */
+ if (pin->nid != mic_pin)
+ snd_hda_codec_read(codec, pin->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+ }
+
+ codec->pins_shutup = 1;
+}
+
+static void alc_shutup_pins(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ case 0x10ec0283:
+ case 0x10ec0286:
+ case 0x10ec0288:
+ case 0x10ec0298:
+ alc_headset_mic_no_shutup(codec);
+ break;
+ default:
+ if (!spec->no_shutup_pins)
+ snd_hda_shutup_pins(codec);
+ break;
+ }
+}
+
+/* generic shutup callback;
+ * just turning off EAPD and a little pause for avoiding pop-noise
+ */
+static void alc_eapd_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_auto_setup_eapd(codec, false);
+ if (!spec->no_depop_delay)
+ msleep(200);
+ alc_shutup_pins(codec);
+}
+
+/* generic EAPD initialization */
+static void alc_auto_init_amp(struct hda_codec *codec, int type)
+{
+ alc_auto_setup_eapd(codec, true);
+ alc_write_gpio(codec);
+ switch (type) {
+ case ALC_INIT_DEFAULT:
+ switch (codec->core.vendor_id) {
+ case 0x10ec0260:
+ alc_update_coefex_idx(codec, 0x1a, 7, 0, 0x2010);
+ break;
+ case 0x10ec0880:
+ case 0x10ec0882:
+ case 0x10ec0883:
+ case 0x10ec0885:
+ alc_update_coef_idx(codec, 7, 0, 0x2030);
+ break;
+ case 0x10ec0888:
+ alc888_coef_init(codec);
+ break;
+ }
+ break;
+ }
+}
+
+/* get a primary headphone pin if available */
+static hda_nid_t alc_get_hp_pin(struct alc_spec *spec)
+{
+ if (spec->gen.autocfg.hp_pins[0])
+ return spec->gen.autocfg.hp_pins[0];
+ if (spec->gen.autocfg.line_out_type == AC_JACK_HP_OUT)
+ return spec->gen.autocfg.line_out_pins[0];
+ return 0;
+}
+
+/*
+ * Realtek SSID verification
+ */
+
+/* Could be any non-zero and even value. When used as fixup, tells
+ * the driver to ignore any present sku defines.
+ */
+#define ALC_FIXUP_SKU_IGNORE (2)
+
+static void alc_fixup_sku_ignore(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->cdefine.fixup = 1;
+ spec->cdefine.sku_cfg = ALC_FIXUP_SKU_IGNORE;
+ }
+}
+
+static void alc_fixup_no_depop_delay(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ spec->no_depop_delay = 1;
+ codec->depop_delay = 0;
+ }
+}
+
+static int alc_auto_parse_customize_define(struct hda_codec *codec)
+{
+ unsigned int ass, tmp, i;
+ unsigned nid = 0;
+ struct alc_spec *spec = codec->spec;
+
+ spec->cdefine.enable_pcbeep = 1; /* assume always enabled */
+
+ if (spec->cdefine.fixup) {
+ ass = spec->cdefine.sku_cfg;
+ if (ass == ALC_FIXUP_SKU_IGNORE)
+ return -1;
+ goto do_sku;
+ }
+
+ if (!codec->bus->pci)
+ return -1;
+ ass = codec->core.subsystem_id & 0xffff;
+ if (ass != codec->bus->pci->subsystem_device && (ass & 1))
+ goto do_sku;
+
+ nid = 0x1d;
+ if (codec->core.vendor_id == 0x10ec0260)
+ nid = 0x17;
+ ass = snd_hda_codec_get_pincfg(codec, nid);
+
+ if (!(ass & 1)) {
+ codec_info(codec, "%s: SKU not ready 0x%08x\n",
+ codec->core.chip_name, ass);
+ return -1;
+ }
+
+ /* check sum */
+ tmp = 0;
+ for (i = 1; i < 16; i++) {
+ if ((ass >> i) & 1)
+ tmp++;
+ }
+ if (((ass >> 16) & 0xf) != tmp)
+ return -1;
+
+ spec->cdefine.port_connectivity = ass >> 30;
+ spec->cdefine.enable_pcbeep = (ass & 0x100000) >> 20;
+ spec->cdefine.check_sum = (ass >> 16) & 0xf;
+ spec->cdefine.customization = ass >> 8;
+do_sku:
+ spec->cdefine.sku_cfg = ass;
+ spec->cdefine.external_amp = (ass & 0x38) >> 3;
+ spec->cdefine.platform_type = (ass & 0x4) >> 2;
+ spec->cdefine.swap = (ass & 0x2) >> 1;
+ spec->cdefine.override = ass & 0x1;
+
+ codec_dbg(codec, "SKU: Nid=0x%x sku_cfg=0x%08x\n",
+ nid, spec->cdefine.sku_cfg);
+ codec_dbg(codec, "SKU: port_connectivity=0x%x\n",
+ spec->cdefine.port_connectivity);
+ codec_dbg(codec, "SKU: enable_pcbeep=0x%x\n", spec->cdefine.enable_pcbeep);
+ codec_dbg(codec, "SKU: check_sum=0x%08x\n", spec->cdefine.check_sum);
+ codec_dbg(codec, "SKU: customization=0x%08x\n", spec->cdefine.customization);
+ codec_dbg(codec, "SKU: external_amp=0x%x\n", spec->cdefine.external_amp);
+ codec_dbg(codec, "SKU: platform_type=0x%x\n", spec->cdefine.platform_type);
+ codec_dbg(codec, "SKU: swap=0x%x\n", spec->cdefine.swap);
+ codec_dbg(codec, "SKU: override=0x%x\n", spec->cdefine.override);
+
+ return 0;
+}
+
+/* return the position of NID in the list, or -1 if not found */
+static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
+{
+ int i;
+ for (i = 0; i < nums; i++)
+ if (list[i] == nid)
+ return i;
+ return -1;
+}
+/* return true if the given NID is found in the list */
+static bool found_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
+{
+ return find_idx_in_nid_list(nid, list, nums) >= 0;
+}
+
+/* check subsystem ID and set up device-specific initialization;
+ * return 1 if initialized, 0 if invalid SSID
+ */
+/* 32-bit subsystem ID for BIOS loading in HD Audio codec.
+ * 31 ~ 16 : Manufacture ID
+ * 15 ~ 8 : SKU ID
+ * 7 ~ 0 : Assembly ID
+ * port-A --> pin 39/41, port-E --> pin 14/15, port-D --> pin 35/36
+ */
+static int alc_subsystem_id(struct hda_codec *codec, const hda_nid_t *ports)
+{
+ unsigned int ass, tmp, i;
+ unsigned nid;
+ struct alc_spec *spec = codec->spec;
+
+ if (spec->cdefine.fixup) {
+ ass = spec->cdefine.sku_cfg;
+ if (ass == ALC_FIXUP_SKU_IGNORE)
+ return 0;
+ goto do_sku;
+ }
+
+ ass = codec->core.subsystem_id & 0xffff;
+ if (codec->bus->pci &&
+ ass != codec->bus->pci->subsystem_device && (ass & 1))
+ goto do_sku;
+
+ /* invalid SSID, check the special NID pin defcfg instead */
+ /*
+ * 31~30 : port connectivity
+ * 29~21 : reserve
+ * 20 : PCBEEP input
+ * 19~16 : Check sum (15:1)
+ * 15~1 : Custom
+ * 0 : override
+ */
+ nid = 0x1d;
+ if (codec->core.vendor_id == 0x10ec0260)
+ nid = 0x17;
+ ass = snd_hda_codec_get_pincfg(codec, nid);
+ codec_dbg(codec,
+ "realtek: No valid SSID, checking pincfg 0x%08x for NID 0x%x\n",
+ ass, nid);
+ if (!(ass & 1))
+ return 0;
+ if ((ass >> 30) != 1) /* no physical connection */
+ return 0;
+
+ /* check sum */
+ tmp = 0;
+ for (i = 1; i < 16; i++) {
+ if ((ass >> i) & 1)
+ tmp++;
+ }
+ if (((ass >> 16) & 0xf) != tmp)
+ return 0;
+do_sku:
+ codec_dbg(codec, "realtek: Enabling init ASM_ID=0x%04x CODEC_ID=%08x\n",
+ ass & 0xffff, codec->core.vendor_id);
+ /*
+ * 0 : override
+ * 1 : Swap Jack
+ * 2 : 0 --> Desktop, 1 --> Laptop
+ * 3~5 : External Amplifier control
+ * 7~6 : Reserved
+ */
+ tmp = (ass & 0x38) >> 3; /* external Amp control */
+ if (spec->init_amp == ALC_INIT_UNDEFINED) {
+ switch (tmp) {
+ case 1:
+ alc_setup_gpio(codec, 0x01);
+ break;
+ case 3:
+ alc_setup_gpio(codec, 0x02);
+ break;
+ case 7:
+ alc_setup_gpio(codec, 0x04);
+ break;
+ case 5:
+ default:
+ spec->init_amp = ALC_INIT_DEFAULT;
+ break;
+ }
+ }
+
+ /* is laptop or Desktop and enable the function "Mute internal speaker
+ * when the external headphone out jack is plugged"
+ */
+ if (!(ass & 0x8000))
+ return 1;
+ /*
+ * 10~8 : Jack location
+ * 12~11: Headphone out -> 00: PortA, 01: PortE, 02: PortD, 03: Resvered
+ * 14~13: Resvered
+ * 15 : 1 --> enable the function "Mute internal speaker
+ * when the external headphone out jack is plugged"
+ */
+ if (!alc_get_hp_pin(spec)) {
+ hda_nid_t nid;
+ tmp = (ass >> 11) & 0x3; /* HP to chassis */
+ nid = ports[tmp];
+ if (found_in_nid_list(nid, spec->gen.autocfg.line_out_pins,
+ spec->gen.autocfg.line_outs))
+ return 1;
+ spec->gen.autocfg.hp_pins[0] = nid;
+ }
+ return 1;
+}
+
+/* Check the validity of ALC subsystem-id
+ * ports contains an array of 4 pin NIDs for port-A, E, D and I */
+static void alc_ssid_check(struct hda_codec *codec, const hda_nid_t *ports)
+{
+ if (!alc_subsystem_id(codec, ports)) {
+ struct alc_spec *spec = codec->spec;
+ if (spec->init_amp == ALC_INIT_UNDEFINED) {
+ codec_dbg(codec,
+ "realtek: Enable default setup for auto mode as fallback\n");
+ spec->init_amp = ALC_INIT_DEFAULT;
+ }
+ }
+}
+
+/*
+ */
+
+static void alc_fixup_inv_dmic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ spec->gen.inv_dmic_split = 1;
+}
+
+
+static int alc_build_controls(struct hda_codec *codec)
+{
+ int err;
+
+ err = snd_hda_gen_build_controls(codec);
+ if (err < 0)
+ return err;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD);
+ return 0;
+}
+
+
+/*
+ * Common callbacks
+ */
+
+static void alc_pre_init(struct hda_codec *codec)
+{
+ alc_fill_eapd_coef(codec);
+}
+
+#define is_s3_resume(codec) \
+ ((codec)->core.dev.power.power_state.event == PM_EVENT_RESUME)
+#define is_s4_resume(codec) \
+ ((codec)->core.dev.power.power_state.event == PM_EVENT_RESTORE)
+
+static int alc_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ /* hibernation resume needs the full chip initialization */
+ if (is_s4_resume(codec))
+ alc_pre_init(codec);
+
+ if (spec->init_hook)
+ spec->init_hook(codec);
+
+ spec->gen.skip_verbs = 1; /* applied in below */
+ snd_hda_gen_init(codec);
+ alc_fix_pll(codec);
+ alc_auto_init_amp(codec, spec->init_amp);
+ snd_hda_apply_verbs(codec); /* apply verbs here after own init */
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
+
+ return 0;
+}
+
+static inline void alc_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (!snd_hda_get_bool_hint(codec, "shutup"))
+ return; /* disabled explicitly by hints */
+
+ if (spec && spec->shutup)
+ spec->shutup(codec);
+ else
+ alc_shutup_pins(codec);
+}
+
+static void alc_reboot_notify(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (spec && spec->reboot_notify)
+ spec->reboot_notify(codec);
+ else
+ alc_shutup(codec);
+}
+
+#define alc_free snd_hda_gen_free
+
+#ifdef CONFIG_PM
+static void alc_power_eapd(struct hda_codec *codec)
+{
+ alc_auto_setup_eapd(codec, false);
+}
+
+static int alc_suspend(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ alc_shutup(codec);
+ if (spec && spec->power_hook)
+ spec->power_hook(codec);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int alc_resume(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (!spec->no_depop_delay)
+ msleep(150); /* to avoid pop noise */
+ codec->patch_ops.init(codec);
+ snd_hda_regmap_sync(codec);
+ hda_call_check_power_status(codec, 0x01);
+ return 0;
+}
+#endif
+
+/*
+ */
+static const struct hda_codec_ops alc_patch_ops = {
+ .build_controls = alc_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = alc_init,
+ .free = alc_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .resume = alc_resume,
+ .suspend = alc_suspend,
+ .check_power_status = snd_hda_gen_check_power_status,
+#endif
+ .reboot_notify = alc_reboot_notify,
+};
+
+
+#define alc_codec_rename(codec, name) snd_hda_codec_set_name(codec, name)
+
+/*
+ * Rename codecs appropriately from COEF value or subvendor id
+ */
+struct alc_codec_rename_table {
+ unsigned int vendor_id;
+ unsigned short coef_mask;
+ unsigned short coef_bits;
+ const char *name;
+};
+
+struct alc_codec_rename_pci_table {
+ unsigned int codec_vendor_id;
+ unsigned short pci_subvendor;
+ unsigned short pci_subdevice;
+ const char *name;
+};
+
+static const struct alc_codec_rename_table rename_tbl[] = {
+ { 0x10ec0221, 0xf00f, 0x1003, "ALC231" },
+ { 0x10ec0269, 0xfff0, 0x3010, "ALC277" },
+ { 0x10ec0269, 0xf0f0, 0x2010, "ALC259" },
+ { 0x10ec0269, 0xf0f0, 0x3010, "ALC258" },
+ { 0x10ec0269, 0x00f0, 0x0010, "ALC269VB" },
+ { 0x10ec0269, 0xffff, 0xa023, "ALC259" },
+ { 0x10ec0269, 0xffff, 0x6023, "ALC281X" },
+ { 0x10ec0269, 0x00f0, 0x0020, "ALC269VC" },
+ { 0x10ec0269, 0x00f0, 0x0030, "ALC269VD" },
+ { 0x10ec0662, 0xffff, 0x4020, "ALC656" },
+ { 0x10ec0887, 0x00f0, 0x0030, "ALC887-VD" },
+ { 0x10ec0888, 0x00f0, 0x0030, "ALC888-VD" },
+ { 0x10ec0888, 0xf0f0, 0x3020, "ALC886" },
+ { 0x10ec0899, 0x2000, 0x2000, "ALC899" },
+ { 0x10ec0892, 0xffff, 0x8020, "ALC661" },
+ { 0x10ec0892, 0xffff, 0x8011, "ALC661" },
+ { 0x10ec0892, 0xffff, 0x4011, "ALC656" },
+ { } /* terminator */
+};
+
+static const struct alc_codec_rename_pci_table rename_pci_tbl[] = {
+ { 0x10ec0280, 0x1028, 0, "ALC3220" },
+ { 0x10ec0282, 0x1028, 0, "ALC3221" },
+ { 0x10ec0283, 0x1028, 0, "ALC3223" },
+ { 0x10ec0288, 0x1028, 0, "ALC3263" },
+ { 0x10ec0292, 0x1028, 0, "ALC3226" },
+ { 0x10ec0293, 0x1028, 0, "ALC3235" },
+ { 0x10ec0255, 0x1028, 0, "ALC3234" },
+ { 0x10ec0668, 0x1028, 0, "ALC3661" },
+ { 0x10ec0275, 0x1028, 0, "ALC3260" },
+ { 0x10ec0899, 0x1028, 0, "ALC3861" },
+ { 0x10ec0298, 0x1028, 0, "ALC3266" },
+ { 0x10ec0236, 0x1028, 0, "ALC3204" },
+ { 0x10ec0256, 0x1028, 0, "ALC3246" },
+ { 0x10ec0225, 0x1028, 0, "ALC3253" },
+ { 0x10ec0295, 0x1028, 0, "ALC3254" },
+ { 0x10ec0299, 0x1028, 0, "ALC3271" },
+ { 0x10ec0670, 0x1025, 0, "ALC669X" },
+ { 0x10ec0676, 0x1025, 0, "ALC679X" },
+ { 0x10ec0282, 0x1043, 0, "ALC3229" },
+ { 0x10ec0233, 0x1043, 0, "ALC3236" },
+ { 0x10ec0280, 0x103c, 0, "ALC3228" },
+ { 0x10ec0282, 0x103c, 0, "ALC3227" },
+ { 0x10ec0286, 0x103c, 0, "ALC3242" },
+ { 0x10ec0290, 0x103c, 0, "ALC3241" },
+ { 0x10ec0668, 0x103c, 0, "ALC3662" },
+ { 0x10ec0283, 0x17aa, 0, "ALC3239" },
+ { 0x10ec0292, 0x17aa, 0, "ALC3232" },
+ { } /* terminator */
+};
+
+static int alc_codec_rename_from_preset(struct hda_codec *codec)
+{
+ const struct alc_codec_rename_table *p;
+ const struct alc_codec_rename_pci_table *q;
+
+ for (p = rename_tbl; p->vendor_id; p++) {
+ if (p->vendor_id != codec->core.vendor_id)
+ continue;
+ if ((alc_get_coef0(codec) & p->coef_mask) == p->coef_bits)
+ return alc_codec_rename(codec, p->name);
+ }
+
+ if (!codec->bus->pci)
+ return 0;
+ for (q = rename_pci_tbl; q->codec_vendor_id; q++) {
+ if (q->codec_vendor_id != codec->core.vendor_id)
+ continue;
+ if (q->pci_subvendor != codec->bus->pci->subsystem_vendor)
+ continue;
+ if (!q->pci_subdevice ||
+ q->pci_subdevice == codec->bus->pci->subsystem_device)
+ return alc_codec_rename(codec, q->name);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Digital-beep handlers
+ */
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+
+/* additional beep mixers; private_value will be overwritten */
+static const struct snd_kcontrol_new alc_beep_mixer[] = {
+ HDA_CODEC_VOLUME("Beep Playback Volume", 0, 0, HDA_INPUT),
+ HDA_CODEC_MUTE_BEEP("Beep Playback Switch", 0, 0, HDA_INPUT),
+};
+
+/* set up and create beep controls */
+static int set_beep_amp(struct alc_spec *spec, hda_nid_t nid,
+ int idx, int dir)
+{
+ struct snd_kcontrol_new *knew;
+ unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(alc_beep_mixer); i++) {
+ knew = snd_hda_gen_add_kctl(&spec->gen, NULL,
+ &alc_beep_mixer[i]);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = beep_amp;
+ }
+ return 0;
+}
+
+static const struct snd_pci_quirk beep_allow_list[] = {
+ SND_PCI_QUIRK(0x1043, 0x103c, "ASUS", 1),
+ SND_PCI_QUIRK(0x1043, 0x115d, "ASUS", 1),
+ SND_PCI_QUIRK(0x1043, 0x829f, "ASUS", 1),
+ SND_PCI_QUIRK(0x1043, 0x8376, "EeePC", 1),
+ SND_PCI_QUIRK(0x1043, 0x83ce, "EeePC", 1),
+ SND_PCI_QUIRK(0x1043, 0x831a, "EeePC", 1),
+ SND_PCI_QUIRK(0x1043, 0x834a, "EeePC", 1),
+ SND_PCI_QUIRK(0x1458, 0xa002, "GA-MA790X", 1),
+ SND_PCI_QUIRK(0x8086, 0xd613, "Intel", 1),
+ /* denylist -- no beep available */
+ SND_PCI_QUIRK(0x17aa, 0x309e, "Lenovo ThinkCentre M73", 0),
+ SND_PCI_QUIRK(0x17aa, 0x30a3, "Lenovo ThinkCentre M93", 0),
+ {}
+};
+
+static inline int has_cdefine_beep(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ const struct snd_pci_quirk *q;
+ q = snd_pci_quirk_lookup(codec->bus->pci, beep_allow_list);
+ if (q)
+ return q->value;
+ return spec->cdefine.enable_pcbeep;
+}
+#else
+#define set_beep_amp(spec, nid, idx, dir) 0
+#define has_cdefine_beep(codec) 0
+#endif
+
+/* parse the BIOS configuration and set up the alc_spec */
+/* return 1 if successful, 0 if the proper config is not found,
+ * or a negative error code
+ */
+static int alc_parse_auto_config(struct hda_codec *codec,
+ const hda_nid_t *ignore_nids,
+ const hda_nid_t *ssid_nids)
+{
+ struct alc_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ int err;
+
+ err = snd_hda_parse_pin_defcfg(codec, cfg, ignore_nids,
+ spec->parse_flags);
+ if (err < 0)
+ return err;
+
+ if (ssid_nids)
+ alc_ssid_check(codec, ssid_nids);
+
+ err = snd_hda_gen_parse_auto_config(codec, cfg);
+ if (err < 0)
+ return err;
+
+ return 1;
+}
+
+/* common preparation job for alc_spec */
+static int alc_alloc_spec(struct hda_codec *codec, hda_nid_t mixer_nid)
+{
+ struct alc_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ int err;
+
+ if (!spec)
+ return -ENOMEM;
+ codec->spec = spec;
+ snd_hda_gen_spec_init(&spec->gen);
+ spec->gen.mixer_nid = mixer_nid;
+ spec->gen.own_eapd_ctl = 1;
+ codec->single_adc_amp = 1;
+ /* FIXME: do we need this for all Realtek codec models? */
+ codec->spdif_status_reset = 1;
+ codec->forced_resume = 1;
+ codec->patch_ops = alc_patch_ops;
+ mutex_init(&spec->coef_mutex);
+
+ err = alc_codec_rename_from_preset(codec);
+ if (err < 0) {
+ kfree(spec);
+ return err;
+ }
+ return 0;
+}
+
+static int alc880_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc880_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc880_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ return alc_parse_auto_config(codec, alc880_ignore, alc880_ssids);
+}
+
+/*
+ * ALC880 fix-ups
+ */
+enum {
+ ALC880_FIXUP_GPIO1,
+ ALC880_FIXUP_GPIO2,
+ ALC880_FIXUP_MEDION_RIM,
+ ALC880_FIXUP_LG,
+ ALC880_FIXUP_LG_LW25,
+ ALC880_FIXUP_W810,
+ ALC880_FIXUP_EAPD_COEF,
+ ALC880_FIXUP_TCL_S700,
+ ALC880_FIXUP_VOL_KNOB,
+ ALC880_FIXUP_FUJITSU,
+ ALC880_FIXUP_F1734,
+ ALC880_FIXUP_UNIWILL,
+ ALC880_FIXUP_UNIWILL_DIG,
+ ALC880_FIXUP_Z71V,
+ ALC880_FIXUP_ASUS_W5A,
+ ALC880_FIXUP_3ST_BASE,
+ ALC880_FIXUP_3ST,
+ ALC880_FIXUP_3ST_DIG,
+ ALC880_FIXUP_5ST_BASE,
+ ALC880_FIXUP_5ST,
+ ALC880_FIXUP_5ST_DIG,
+ ALC880_FIXUP_6ST_BASE,
+ ALC880_FIXUP_6ST,
+ ALC880_FIXUP_6ST_DIG,
+ ALC880_FIXUP_6ST_AUTOMUTE,
+};
+
+/* enable the volume-knob widget support on NID 0x21 */
+static void alc880_fixup_vol_knob(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PROBE)
+ snd_hda_jack_detect_enable_callback(codec, 0x21,
+ alc_update_knob_master);
+}
+
+static const struct hda_fixup alc880_fixups[] = {
+ [ALC880_FIXUP_GPIO1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio1,
+ },
+ [ALC880_FIXUP_GPIO2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio2,
+ },
+ [ALC880_FIXUP_MEDION_RIM] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3060 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_GPIO2,
+ },
+ [ALC880_FIXUP_LG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* disable bogus unused pins */
+ { 0x16, 0x411111f0 },
+ { 0x18, 0x411111f0 },
+ { 0x1a, 0x411111f0 },
+ { }
+ }
+ },
+ [ALC880_FIXUP_LG_LW25] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x0181344f }, /* line-in */
+ { 0x1b, 0x0321403f }, /* headphone */
+ { }
+ }
+ },
+ [ALC880_FIXUP_W810] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* disable bogus unused pins */
+ { 0x17, 0x411111f0 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_GPIO2,
+ },
+ [ALC880_FIXUP_EAPD_COEF] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* change to EAPD mode */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3060 },
+ {}
+ },
+ },
+ [ALC880_FIXUP_TCL_S700] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* change to EAPD mode */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3070 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_GPIO2,
+ },
+ [ALC880_FIXUP_VOL_KNOB] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc880_fixup_vol_knob,
+ },
+ [ALC880_FIXUP_FUJITSU] = {
+ /* override all pins as BIOS on old Amilo is broken */
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x0121401f }, /* HP */
+ { 0x15, 0x99030120 }, /* speaker */
+ { 0x16, 0x99030130 }, /* bass speaker */
+ { 0x17, 0x411111f0 }, /* N/A */
+ { 0x18, 0x411111f0 }, /* N/A */
+ { 0x19, 0x01a19950 }, /* mic-in */
+ { 0x1a, 0x411111f0 }, /* N/A */
+ { 0x1b, 0x411111f0 }, /* N/A */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ { 0x1e, 0x01454140 }, /* SPDIF out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_VOL_KNOB,
+ },
+ [ALC880_FIXUP_F1734] = {
+ /* almost compatible with FUJITSU, but no bass and SPDIF */
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x0121401f }, /* HP */
+ { 0x15, 0x99030120 }, /* speaker */
+ { 0x16, 0x411111f0 }, /* N/A */
+ { 0x17, 0x411111f0 }, /* N/A */
+ { 0x18, 0x411111f0 }, /* N/A */
+ { 0x19, 0x01a19950 }, /* mic-in */
+ { 0x1a, 0x411111f0 }, /* N/A */
+ { 0x1b, 0x411111f0 }, /* N/A */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_VOL_KNOB,
+ },
+ [ALC880_FIXUP_UNIWILL] = {
+ /* need to fix HP and speaker pins to be parsed correctly */
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x0121411f }, /* HP */
+ { 0x15, 0x99030120 }, /* speaker */
+ { 0x16, 0x99030130 }, /* bass speaker */
+ { }
+ },
+ },
+ [ALC880_FIXUP_UNIWILL_DIG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* disable bogus unused pins */
+ { 0x17, 0x411111f0 },
+ { 0x19, 0x411111f0 },
+ { 0x1b, 0x411111f0 },
+ { 0x1f, 0x411111f0 },
+ { }
+ }
+ },
+ [ALC880_FIXUP_Z71V] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* set up the whole pins as BIOS is utterly broken */
+ { 0x14, 0x99030120 }, /* speaker */
+ { 0x15, 0x0121411f }, /* HP */
+ { 0x16, 0x411111f0 }, /* N/A */
+ { 0x17, 0x411111f0 }, /* N/A */
+ { 0x18, 0x01a19950 }, /* mic-in */
+ { 0x19, 0x411111f0 }, /* N/A */
+ { 0x1a, 0x01813031 }, /* line-in */
+ { 0x1b, 0x411111f0 }, /* N/A */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ { 0x1e, 0x0144111e }, /* SPDIF */
+ { }
+ }
+ },
+ [ALC880_FIXUP_ASUS_W5A] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* set up the whole pins as BIOS is utterly broken */
+ { 0x14, 0x0121411f }, /* HP */
+ { 0x15, 0x411111f0 }, /* N/A */
+ { 0x16, 0x411111f0 }, /* N/A */
+ { 0x17, 0x411111f0 }, /* N/A */
+ { 0x18, 0x90a60160 }, /* mic */
+ { 0x19, 0x411111f0 }, /* N/A */
+ { 0x1a, 0x411111f0 }, /* N/A */
+ { 0x1b, 0x411111f0 }, /* N/A */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ { 0x1e, 0xb743111e }, /* SPDIF out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_GPIO1,
+ },
+ [ALC880_FIXUP_3ST_BASE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x01014010 }, /* line-out */
+ { 0x15, 0x411111f0 }, /* N/A */
+ { 0x16, 0x411111f0 }, /* N/A */
+ { 0x17, 0x411111f0 }, /* N/A */
+ { 0x18, 0x01a19c30 }, /* mic-in */
+ { 0x19, 0x0121411f }, /* HP */
+ { 0x1a, 0x01813031 }, /* line-in */
+ { 0x1b, 0x02a19c40 }, /* front-mic */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ /* 0x1e is filled in below */
+ { 0x1f, 0x411111f0 }, /* N/A */
+ { }
+ }
+ },
+ [ALC880_FIXUP_3ST] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_3ST_BASE,
+ },
+ [ALC880_FIXUP_3ST_DIG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x0144111e }, /* SPDIF */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_3ST_BASE,
+ },
+ [ALC880_FIXUP_5ST_BASE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x01014010 }, /* front */
+ { 0x15, 0x411111f0 }, /* N/A */
+ { 0x16, 0x01011411 }, /* CLFE */
+ { 0x17, 0x01016412 }, /* surr */
+ { 0x18, 0x01a19c30 }, /* mic-in */
+ { 0x19, 0x0121411f }, /* HP */
+ { 0x1a, 0x01813031 }, /* line-in */
+ { 0x1b, 0x02a19c40 }, /* front-mic */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ /* 0x1e is filled in below */
+ { 0x1f, 0x411111f0 }, /* N/A */
+ { }
+ }
+ },
+ [ALC880_FIXUP_5ST] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_5ST_BASE,
+ },
+ [ALC880_FIXUP_5ST_DIG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x0144111e }, /* SPDIF */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_5ST_BASE,
+ },
+ [ALC880_FIXUP_6ST_BASE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x01014010 }, /* front */
+ { 0x15, 0x01016412 }, /* surr */
+ { 0x16, 0x01011411 }, /* CLFE */
+ { 0x17, 0x01012414 }, /* side */
+ { 0x18, 0x01a19c30 }, /* mic-in */
+ { 0x19, 0x02a19c40 }, /* front-mic */
+ { 0x1a, 0x01813031 }, /* line-in */
+ { 0x1b, 0x0121411f }, /* HP */
+ { 0x1c, 0x411111f0 }, /* N/A */
+ { 0x1d, 0x411111f0 }, /* N/A */
+ /* 0x1e is filled in below */
+ { 0x1f, 0x411111f0 }, /* N/A */
+ { }
+ }
+ },
+ [ALC880_FIXUP_6ST] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x411111f0 }, /* N/A */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_6ST_BASE,
+ },
+ [ALC880_FIXUP_6ST_DIG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x0144111e }, /* SPDIF */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC880_FIXUP_6ST_BASE,
+ },
+ [ALC880_FIXUP_6ST_AUTOMUTE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x0121401f }, /* HP with jack detect */
+ { }
+ },
+ .chained_before = true,
+ .chain_id = ALC880_FIXUP_6ST_BASE,
+ },
+};
+
+static const struct snd_pci_quirk alc880_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1019, 0x0f69, "Coeus G610P", ALC880_FIXUP_W810),
+ SND_PCI_QUIRK(0x1043, 0x10c3, "ASUS W5A", ALC880_FIXUP_ASUS_W5A),
+ SND_PCI_QUIRK(0x1043, 0x1964, "ASUS Z71V", ALC880_FIXUP_Z71V),
+ SND_PCI_QUIRK_VENDOR(0x1043, "ASUS", ALC880_FIXUP_GPIO1),
+ SND_PCI_QUIRK(0x147b, 0x1045, "ABit AA8XE", ALC880_FIXUP_6ST_AUTOMUTE),
+ SND_PCI_QUIRK(0x1558, 0x5401, "Clevo GPIO2", ALC880_FIXUP_GPIO2),
+ SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", ALC880_FIXUP_EAPD_COEF),
+ SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_FIXUP_UNIWILL_DIG),
+ SND_PCI_QUIRK(0x1584, 0x9054, "Uniwill", ALC880_FIXUP_F1734),
+ SND_PCI_QUIRK(0x1584, 0x9070, "Uniwill", ALC880_FIXUP_UNIWILL),
+ SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_FIXUP_VOL_KNOB),
+ SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_FIXUP_W810),
+ SND_PCI_QUIRK(0x161f, 0x205d, "Medion Rim 2150", ALC880_FIXUP_MEDION_RIM),
+ SND_PCI_QUIRK(0x1631, 0xe011, "PB 13201056", ALC880_FIXUP_6ST_AUTOMUTE),
+ SND_PCI_QUIRK(0x1734, 0x107c, "FSC Amilo M1437", ALC880_FIXUP_FUJITSU),
+ SND_PCI_QUIRK(0x1734, 0x1094, "FSC Amilo M1451G", ALC880_FIXUP_FUJITSU),
+ SND_PCI_QUIRK(0x1734, 0x10ac, "FSC AMILO Xi 1526", ALC880_FIXUP_F1734),
+ SND_PCI_QUIRK(0x1734, 0x10b0, "FSC Amilo Pi1556", ALC880_FIXUP_FUJITSU),
+ SND_PCI_QUIRK(0x1854, 0x003b, "LG", ALC880_FIXUP_LG),
+ SND_PCI_QUIRK(0x1854, 0x005f, "LG P1 Express", ALC880_FIXUP_LG),
+ SND_PCI_QUIRK(0x1854, 0x0068, "LG w1", ALC880_FIXUP_LG),
+ SND_PCI_QUIRK(0x1854, 0x0077, "LG LW25", ALC880_FIXUP_LG_LW25),
+ SND_PCI_QUIRK(0x19db, 0x4188, "TCL S700", ALC880_FIXUP_TCL_S700),
+
+ /* Below is the copied entries from alc880_quirks.c.
+ * It's not quite sure whether BIOS sets the correct pin-config table
+ * on these machines, thus they are kept to be compatible with
+ * the old static quirks. Once when it's confirmed to work without
+ * these overrides, it'd be better to remove.
+ */
+ SND_PCI_QUIRK(0x1019, 0xa880, "ECS", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x1019, 0xa884, "Acer APFV", ALC880_FIXUP_6ST),
+ SND_PCI_QUIRK(0x1025, 0x0070, "ULI", ALC880_FIXUP_3ST_DIG),
+ SND_PCI_QUIRK(0x1025, 0x0077, "ULI", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1025, 0x0078, "ULI", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1025, 0x0087, "ULI", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1025, 0xe309, "ULI", ALC880_FIXUP_3ST_DIG),
+ SND_PCI_QUIRK(0x1025, 0xe310, "ULI", ALC880_FIXUP_3ST),
+ SND_PCI_QUIRK(0x1039, 0x1234, NULL, ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x104d, 0x81a0, "Sony", ALC880_FIXUP_3ST),
+ SND_PCI_QUIRK(0x104d, 0x81d6, "Sony", ALC880_FIXUP_3ST),
+ SND_PCI_QUIRK(0x107b, 0x3032, "Gateway", ALC880_FIXUP_5ST),
+ SND_PCI_QUIRK(0x107b, 0x3033, "Gateway", ALC880_FIXUP_5ST),
+ SND_PCI_QUIRK(0x107b, 0x4039, "Gateway", ALC880_FIXUP_5ST),
+ SND_PCI_QUIRK(0x1297, 0xc790, "Shuttle ST20G5", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1458, 0xa102, "Gigabyte K8", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1462, 0x1150, "MSI", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1509, 0x925d, "FIC P4M", ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x1565, 0x8202, "Biostar", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x1695, 0x400d, "EPoX", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x1695, 0x4012, "EPox EP-5LDA", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x2668, 0x8086, NULL, ALC880_FIXUP_6ST_DIG), /* broken BIOS */
+ SND_PCI_QUIRK(0x8086, 0x2668, NULL, ALC880_FIXUP_6ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xa100, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xd400, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xd401, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xd402, "Intel mobo", ALC880_FIXUP_3ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xe224, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xe305, "Intel mobo", ALC880_FIXUP_3ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xe308, "Intel mobo", ALC880_FIXUP_3ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xe400, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xe401, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0x8086, 0xe402, "Intel mobo", ALC880_FIXUP_5ST_DIG),
+ /* default Intel */
+ SND_PCI_QUIRK_VENDOR(0x8086, "Intel mobo", ALC880_FIXUP_3ST),
+ SND_PCI_QUIRK(0xa0a0, 0x0560, "AOpen i915GMm-HFS", ALC880_FIXUP_5ST_DIG),
+ SND_PCI_QUIRK(0xe803, 0x1019, NULL, ALC880_FIXUP_6ST_DIG),
+ {}
+};
+
+static const struct hda_model_fixup alc880_fixup_models[] = {
+ {.id = ALC880_FIXUP_3ST, .name = "3stack"},
+ {.id = ALC880_FIXUP_3ST_DIG, .name = "3stack-digout"},
+ {.id = ALC880_FIXUP_5ST, .name = "5stack"},
+ {.id = ALC880_FIXUP_5ST_DIG, .name = "5stack-digout"},
+ {.id = ALC880_FIXUP_6ST, .name = "6stack"},
+ {.id = ALC880_FIXUP_6ST_DIG, .name = "6stack-digout"},
+ {.id = ALC880_FIXUP_6ST_AUTOMUTE, .name = "6stack-automute"},
+ {}
+};
+
+
+/*
+ * OK, here we have finally the patch for ALC880
+ */
+static int patch_alc880(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x0b);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->gen.need_dac_fix = 1;
+ spec->gen.beep_nid = 0x01;
+
+ codec->patch_ops.unsol_event = alc880_unsol_event;
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc880_fixup_models, alc880_fixup_tbl,
+ alc880_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ /* automatic parse from the BIOS config */
+ err = alc880_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog) {
+ err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+
+/*
+ * ALC260 support
+ */
+static int alc260_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc260_ignore[] = { 0x17, 0 };
+ static const hda_nid_t alc260_ssids[] = { 0x10, 0x15, 0x0f, 0 };
+ return alc_parse_auto_config(codec, alc260_ignore, alc260_ssids);
+}
+
+/*
+ * Pin config fixes
+ */
+enum {
+ ALC260_FIXUP_HP_DC5750,
+ ALC260_FIXUP_HP_PIN_0F,
+ ALC260_FIXUP_COEF,
+ ALC260_FIXUP_GPIO1,
+ ALC260_FIXUP_GPIO1_TOGGLE,
+ ALC260_FIXUP_REPLACER,
+ ALC260_FIXUP_HP_B1900,
+ ALC260_FIXUP_KN1,
+ ALC260_FIXUP_FSC_S7020,
+ ALC260_FIXUP_FSC_S7020_JWSE,
+ ALC260_FIXUP_VAIO_PINS,
+};
+
+static void alc260_gpio1_automute(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_gpio_data(codec, 0x01, spec->gen.hp_jack_present);
+}
+
+static void alc260_fixup_gpio1_toggle(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ /* although the machine has only one output pin, we need to
+ * toggle GPIO1 according to the jack state
+ */
+ spec->gen.automute_hook = alc260_gpio1_automute;
+ spec->gen.detect_hp = 1;
+ spec->gen.automute_speaker = 1;
+ spec->gen.autocfg.hp_pins[0] = 0x0f; /* copy it for automute */
+ snd_hda_jack_detect_enable_callback(codec, 0x0f,
+ snd_hda_gen_hp_automute);
+ alc_setup_gpio(codec, 0x01);
+ }
+}
+
+static void alc260_fixup_kn1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x0f, 0x02214000 }, /* HP/speaker */
+ { 0x12, 0x90a60160 }, /* int mic */
+ { 0x13, 0x02a19000 }, /* ext mic */
+ { 0x18, 0x01446000 }, /* SPDIF out */
+ /* disable bogus I/O pins */
+ { 0x10, 0x411111f0 },
+ { 0x11, 0x411111f0 },
+ { 0x14, 0x411111f0 },
+ { 0x15, 0x411111f0 },
+ { 0x16, 0x411111f0 },
+ { 0x17, 0x411111f0 },
+ { 0x19, 0x411111f0 },
+ { }
+ };
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ spec->init_amp = ALC_INIT_NONE;
+ break;
+ }
+}
+
+static void alc260_fixup_fsc_s7020(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->init_amp = ALC_INIT_NONE;
+}
+
+static void alc260_fixup_fsc_s7020_jwse(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gen.add_jack_modes = 1;
+ spec->gen.hp_mic = 1;
+ }
+}
+
+static const struct hda_fixup alc260_fixups[] = {
+ [ALC260_FIXUP_HP_DC5750] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x11, 0x90130110 }, /* speaker */
+ { }
+ }
+ },
+ [ALC260_FIXUP_HP_PIN_0F] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x0f, 0x01214000 }, /* HP */
+ { }
+ }
+ },
+ [ALC260_FIXUP_COEF] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x1a, AC_VERB_SET_PROC_COEF, 0x3040 },
+ { }
+ },
+ },
+ [ALC260_FIXUP_GPIO1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio1,
+ },
+ [ALC260_FIXUP_GPIO1_TOGGLE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc260_fixup_gpio1_toggle,
+ .chained = true,
+ .chain_id = ALC260_FIXUP_HP_PIN_0F,
+ },
+ [ALC260_FIXUP_REPLACER] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x1a, AC_VERB_SET_PROC_COEF, 0x3050 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC260_FIXUP_GPIO1_TOGGLE,
+ },
+ [ALC260_FIXUP_HP_B1900] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc260_fixup_gpio1_toggle,
+ .chained = true,
+ .chain_id = ALC260_FIXUP_COEF,
+ },
+ [ALC260_FIXUP_KN1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc260_fixup_kn1,
+ },
+ [ALC260_FIXUP_FSC_S7020] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc260_fixup_fsc_s7020,
+ },
+ [ALC260_FIXUP_FSC_S7020_JWSE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc260_fixup_fsc_s7020_jwse,
+ .chained = true,
+ .chain_id = ALC260_FIXUP_FSC_S7020,
+ },
+ [ALC260_FIXUP_VAIO_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* Pin configs are missing completely on some VAIOs */
+ { 0x0f, 0x01211020 },
+ { 0x10, 0x0001003f },
+ { 0x11, 0x411111f0 },
+ { 0x12, 0x01a15930 },
+ { 0x13, 0x411111f0 },
+ { 0x14, 0x411111f0 },
+ { 0x15, 0x411111f0 },
+ { 0x16, 0x411111f0 },
+ { 0x17, 0x411111f0 },
+ { 0x18, 0x411111f0 },
+ { 0x19, 0x411111f0 },
+ { }
+ }
+ },
+};
+
+static const struct snd_pci_quirk alc260_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1025, 0x007b, "Acer C20x", ALC260_FIXUP_GPIO1),
+ SND_PCI_QUIRK(0x1025, 0x007f, "Acer Aspire 9500", ALC260_FIXUP_COEF),
+ SND_PCI_QUIRK(0x1025, 0x008f, "Acer", ALC260_FIXUP_GPIO1),
+ SND_PCI_QUIRK(0x103c, 0x280a, "HP dc5750", ALC260_FIXUP_HP_DC5750),
+ SND_PCI_QUIRK(0x103c, 0x30ba, "HP Presario B1900", ALC260_FIXUP_HP_B1900),
+ SND_PCI_QUIRK(0x104d, 0x81bb, "Sony VAIO", ALC260_FIXUP_VAIO_PINS),
+ SND_PCI_QUIRK(0x104d, 0x81e2, "Sony VAIO TX", ALC260_FIXUP_HP_PIN_0F),
+ SND_PCI_QUIRK(0x10cf, 0x1326, "FSC LifeBook S7020", ALC260_FIXUP_FSC_S7020),
+ SND_PCI_QUIRK(0x1509, 0x4540, "Favorit 100XS", ALC260_FIXUP_GPIO1),
+ SND_PCI_QUIRK(0x152d, 0x0729, "Quanta KN1", ALC260_FIXUP_KN1),
+ SND_PCI_QUIRK(0x161f, 0x2057, "Replacer 672V", ALC260_FIXUP_REPLACER),
+ SND_PCI_QUIRK(0x1631, 0xc017, "PB V7900", ALC260_FIXUP_COEF),
+ {}
+};
+
+static const struct hda_model_fixup alc260_fixup_models[] = {
+ {.id = ALC260_FIXUP_GPIO1, .name = "gpio1"},
+ {.id = ALC260_FIXUP_COEF, .name = "coef"},
+ {.id = ALC260_FIXUP_FSC_S7020, .name = "fujitsu"},
+ {.id = ALC260_FIXUP_FSC_S7020_JWSE, .name = "fujitsu-jwse"},
+ {}
+};
+
+/*
+ */
+static int patch_alc260(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x07);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ /* as quite a few machines require HP amp for speaker outputs,
+ * it's easier to enable it unconditionally; even if it's unneeded,
+ * it's almost harmless.
+ */
+ spec->gen.prefer_hp_amp = 1;
+ spec->gen.beep_nid = 0x01;
+
+ spec->shutup = alc_eapd_shutup;
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc260_fixup_models, alc260_fixup_tbl,
+ alc260_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ /* automatic parse from the BIOS config */
+ err = alc260_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog) {
+ err = set_beep_amp(spec, 0x07, 0x05, HDA_INPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+
+/*
+ * ALC882/883/885/888/889 support
+ *
+ * ALC882 is almost identical with ALC880 but has cleaner and more flexible
+ * configuration. Each pin widget can choose any input DACs and a mixer.
+ * Each ADC is connected from a mixer of all inputs. This makes possible
+ * 6-channel independent captures.
+ *
+ * In addition, an independent DAC for the multi-playback (not used in this
+ * driver yet).
+ */
+
+/*
+ * Pin config fixes
+ */
+enum {
+ ALC882_FIXUP_ABIT_AW9D_MAX,
+ ALC882_FIXUP_LENOVO_Y530,
+ ALC882_FIXUP_PB_M5210,
+ ALC882_FIXUP_ACER_ASPIRE_7736,
+ ALC882_FIXUP_ASUS_W90V,
+ ALC889_FIXUP_CD,
+ ALC889_FIXUP_FRONT_HP_NO_PRESENCE,
+ ALC889_FIXUP_VAIO_TT,
+ ALC888_FIXUP_EEE1601,
+ ALC886_FIXUP_EAPD,
+ ALC882_FIXUP_EAPD,
+ ALC883_FIXUP_EAPD,
+ ALC883_FIXUP_ACER_EAPD,
+ ALC882_FIXUP_GPIO1,
+ ALC882_FIXUP_GPIO2,
+ ALC882_FIXUP_GPIO3,
+ ALC889_FIXUP_COEF,
+ ALC882_FIXUP_ASUS_W2JC,
+ ALC882_FIXUP_ACER_ASPIRE_4930G,
+ ALC882_FIXUP_ACER_ASPIRE_8930G,
+ ALC882_FIXUP_ASPIRE_8930G_VERBS,
+ ALC885_FIXUP_MACPRO_GPIO,
+ ALC889_FIXUP_DAC_ROUTE,
+ ALC889_FIXUP_MBP_VREF,
+ ALC889_FIXUP_IMAC91_VREF,
+ ALC889_FIXUP_MBA11_VREF,
+ ALC889_FIXUP_MBA21_VREF,
+ ALC889_FIXUP_MP11_VREF,
+ ALC889_FIXUP_MP41_VREF,
+ ALC882_FIXUP_INV_DMIC,
+ ALC882_FIXUP_NO_PRIMARY_HP,
+ ALC887_FIXUP_ASUS_BASS,
+ ALC887_FIXUP_BASS_CHMAP,
+ ALC1220_FIXUP_GB_DUAL_CODECS,
+ ALC1220_FIXUP_GB_X570,
+ ALC1220_FIXUP_CLEVO_P950,
+ ALC1220_FIXUP_CLEVO_PB51ED,
+ ALC1220_FIXUP_CLEVO_PB51ED_PINS,
+ ALC887_FIXUP_ASUS_AUDIO,
+ ALC887_FIXUP_ASUS_HMIC,
+ ALCS1200A_FIXUP_MIC_VREF,
+ ALC888VD_FIXUP_MIC_100VREF,
+};
+
+static void alc889_fixup_coef(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+ alc_update_coef_idx(codec, 7, 0, 0x2030);
+}
+
+/* set up GPIO at initialization */
+static void alc885_fixup_macpro_gpio(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ spec->gpio_write_delay = true;
+ alc_fixup_gpio3(codec, fix, action);
+}
+
+/* Fix the connection of some pins for ALC889:
+ * At least, Acer Aspire 5935 shows the connections to DAC3/4 don't
+ * work correctly (bko#42740)
+ */
+static void alc889_fixup_dac_route(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ /* fake the connections during parsing the tree */
+ static const hda_nid_t conn1[] = { 0x0c, 0x0d };
+ static const hda_nid_t conn2[] = { 0x0e, 0x0f };
+ snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1);
+ snd_hda_override_conn_list(codec, 0x15, ARRAY_SIZE(conn1), conn1);
+ snd_hda_override_conn_list(codec, 0x18, ARRAY_SIZE(conn2), conn2);
+ snd_hda_override_conn_list(codec, 0x1a, ARRAY_SIZE(conn2), conn2);
+ } else if (action == HDA_FIXUP_ACT_PROBE) {
+ /* restore the connections */
+ static const hda_nid_t conn[] = { 0x0c, 0x0d, 0x0e, 0x0f, 0x26 };
+ snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn), conn);
+ snd_hda_override_conn_list(codec, 0x15, ARRAY_SIZE(conn), conn);
+ snd_hda_override_conn_list(codec, 0x18, ARRAY_SIZE(conn), conn);
+ snd_hda_override_conn_list(codec, 0x1a, ARRAY_SIZE(conn), conn);
+ }
+}
+
+/* Set VREF on HP pin */
+static void alc889_fixup_mbp_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t nids[] = { 0x14, 0x15, 0x19 };
+ struct alc_spec *spec = codec->spec;
+ int i;
+
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+ for (i = 0; i < ARRAY_SIZE(nids); i++) {
+ unsigned int val = snd_hda_codec_get_pincfg(codec, nids[i]);
+ if (get_defcfg_device(val) != AC_JACK_HP_OUT)
+ continue;
+ val = snd_hda_codec_get_pin_target(codec, nids[i]);
+ val |= AC_PINCTL_VREF_80;
+ snd_hda_set_pin_ctl(codec, nids[i], val);
+ spec->gen.keep_vref_in_automute = 1;
+ break;
+ }
+}
+
+static void alc889_fixup_mac_pins(struct hda_codec *codec,
+ const hda_nid_t *nids, int num_nids)
+{
+ struct alc_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < num_nids; i++) {
+ unsigned int val;
+ val = snd_hda_codec_get_pin_target(codec, nids[i]);
+ val |= AC_PINCTL_VREF_50;
+ snd_hda_set_pin_ctl(codec, nids[i], val);
+ }
+ spec->gen.keep_vref_in_automute = 1;
+}
+
+/* Set VREF on speaker pins on imac91 */
+static void alc889_fixup_imac91_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t nids[] = { 0x18, 0x1a };
+
+ if (action == HDA_FIXUP_ACT_INIT)
+ alc889_fixup_mac_pins(codec, nids, ARRAY_SIZE(nids));
+}
+
+/* Set VREF on speaker pins on mba11 */
+static void alc889_fixup_mba11_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t nids[] = { 0x18 };
+
+ if (action == HDA_FIXUP_ACT_INIT)
+ alc889_fixup_mac_pins(codec, nids, ARRAY_SIZE(nids));
+}
+
+/* Set VREF on speaker pins on mba21 */
+static void alc889_fixup_mba21_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t nids[] = { 0x18, 0x19 };
+
+ if (action == HDA_FIXUP_ACT_INIT)
+ alc889_fixup_mac_pins(codec, nids, ARRAY_SIZE(nids));
+}
+
+/* Don't take HP output as primary
+ * Strangely, the speaker output doesn't work on Vaio Z and some Vaio
+ * all-in-one desktop PCs (for example VGC-LN51JGB) through DAC 0x05
+ */
+static void alc882_fixup_no_primary_hp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gen.no_primary_hp = 1;
+ spec->gen.no_multi_io = 1;
+ }
+}
+
+static void alc_fixup_bass_chmap(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action);
+
+/* For dual-codec configuration, we need to disable some features to avoid
+ * conflicts of kctls and PCM streams
+ */
+static void alc_fixup_dual_codecs(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ /* disable vmaster */
+ spec->gen.suppress_vmaster = 1;
+ /* auto-mute and auto-mic switch don't work with multiple codecs */
+ spec->gen.suppress_auto_mute = 1;
+ spec->gen.suppress_auto_mic = 1;
+ /* disable aamix as well */
+ spec->gen.mixer_nid = 0;
+ /* add location prefix to avoid conflicts */
+ codec->force_pin_prefix = 1;
+}
+
+static void rename_ctl(struct hda_codec *codec, const char *oldname,
+ const char *newname)
+{
+ struct snd_kcontrol *kctl;
+
+ kctl = snd_hda_find_mixer_ctl(codec, oldname);
+ if (kctl)
+ strcpy(kctl->id.name, newname);
+}
+
+static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ alc_fixup_dual_codecs(codec, fix, action);
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ /* override card longname to provide a unique UCM profile */
+ strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs");
+ break;
+ case HDA_FIXUP_ACT_BUILD:
+ /* rename Capture controls depending on the codec */
+ rename_ctl(codec, "Capture Volume",
+ codec->addr == 0 ?
+ "Rear-Panel Capture Volume" :
+ "Front-Panel Capture Volume");
+ rename_ctl(codec, "Capture Switch",
+ codec->addr == 0 ?
+ "Rear-Panel Capture Switch" :
+ "Front-Panel Capture Switch");
+ break;
+ }
+}
+
+static void alc1220_fixup_gb_x570(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ static const hda_nid_t conn1[] = { 0x0c };
+ static const struct coef_fw gb_x570_coefs[] = {
+ WRITE_COEF(0x07, 0x03c0),
+ WRITE_COEF(0x1a, 0x01c1),
+ WRITE_COEF(0x1b, 0x0202),
+ WRITE_COEF(0x43, 0x3005),
+ {}
+ };
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1);
+ snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn1), conn1);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc_process_coef_fw(codec, gb_x570_coefs);
+ break;
+ }
+}
+
+static void alc1220_fixup_clevo_p950(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ static const hda_nid_t conn1[] = { 0x0c };
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ alc_update_coef_idx(codec, 0x7, 0, 0x3c3);
+ /* We therefore want to make sure 0x14 (front headphone) and
+ * 0x1b (speakers) use the stereo DAC 0x02
+ */
+ snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1);
+ snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn1), conn1);
+}
+
+static void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action);
+
+static void alc1220_fixup_clevo_pb51ed(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ alc1220_fixup_clevo_p950(codec, fix, action);
+ alc_fixup_headset_mode_no_hp_mic(codec, fix, action);
+}
+
+static void alc887_asus_hp_automute_hook(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+ unsigned int vref;
+
+ snd_hda_gen_hp_automute(codec, jack);
+
+ if (spec->gen.hp_jack_present)
+ vref = AC_PINCTL_VREF_80;
+ else
+ vref = AC_PINCTL_VREF_HIZ;
+ snd_hda_set_pin_ctl(codec, 0x19, PIN_HP | vref);
+}
+
+static void alc887_fixup_asus_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action != HDA_FIXUP_ACT_PROBE)
+ return;
+ snd_hda_set_pin_ctl_cache(codec, 0x1b, PIN_HP);
+ spec->gen.hp_automute_hook = alc887_asus_hp_automute_hook;
+}
+
+static const struct hda_fixup alc882_fixups[] = {
+ [ALC882_FIXUP_ABIT_AW9D_MAX] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x01080104 }, /* side */
+ { 0x16, 0x01011012 }, /* rear */
+ { 0x17, 0x01016011 }, /* clfe */
+ { }
+ }
+ },
+ [ALC882_FIXUP_LENOVO_Y530] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x99130112 }, /* rear int speakers */
+ { 0x16, 0x99130111 }, /* subwoofer */
+ { }
+ }
+ },
+ [ALC882_FIXUP_PB_M5210] = {
+ .type = HDA_FIXUP_PINCTLS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, PIN_VREF50 },
+ {}
+ }
+ },
+ [ALC882_FIXUP_ACER_ASPIRE_7736] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_sku_ignore,
+ },
+ [ALC882_FIXUP_ASUS_W90V] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x99130110 }, /* fix sequence for CLFE */
+ { }
+ }
+ },
+ [ALC889_FIXUP_CD] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1c, 0x993301f0 }, /* CD */
+ { }
+ }
+ },
+ [ALC889_FIXUP_FRONT_HP_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x02214120 }, /* Front HP jack is flaky, disable jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC889_FIXUP_CD,
+ },
+ [ALC889_FIXUP_VAIO_TT] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x17, 0x90170111 }, /* hidden surround speaker */
+ { }
+ }
+ },
+ [ALC888_FIXUP_EEE1601] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x0b },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0838 },
+ { }
+ }
+ },
+ [ALC886_FIXUP_EAPD] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* change to EAPD mode */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0068 },
+ { }
+ }
+ },
+ [ALC882_FIXUP_EAPD] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* change to EAPD mode */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3060 },
+ { }
+ }
+ },
+ [ALC883_FIXUP_EAPD] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* change to EAPD mode */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3070 },
+ { }
+ }
+ },
+ [ALC883_FIXUP_ACER_EAPD] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* eanable EAPD on Acer laptops */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 },
+ { }
+ }
+ },
+ [ALC882_FIXUP_GPIO1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio1,
+ },
+ [ALC882_FIXUP_GPIO2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio2,
+ },
+ [ALC882_FIXUP_GPIO3] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio3,
+ },
+ [ALC882_FIXUP_ASUS_W2JC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio1,
+ .chained = true,
+ .chain_id = ALC882_FIXUP_EAPD,
+ },
+ [ALC889_FIXUP_COEF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_coef,
+ },
+ [ALC882_FIXUP_ACER_ASPIRE_4930G] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x99130111 }, /* CLFE speaker */
+ { 0x17, 0x99130112 }, /* surround speaker */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC882_FIXUP_GPIO1,
+ },
+ [ALC882_FIXUP_ACER_ASPIRE_8930G] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x99130111 }, /* CLFE speaker */
+ { 0x1b, 0x99130112 }, /* surround speaker */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC882_FIXUP_ASPIRE_8930G_VERBS,
+ },
+ [ALC882_FIXUP_ASPIRE_8930G_VERBS] = {
+ /* additional init verbs for Acer Aspire 8930G */
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Enable all DACs */
+ /* DAC DISABLE/MUTE 1? */
+ /* setting bits 1-5 disables DAC nids 0x02-0x06
+ * apparently. Init=0x38 */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x03 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+ /* DAC DISABLE/MUTE 2? */
+ /* some bit here disables the other DACs.
+ * Init=0x4900 */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x08 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0000 },
+ /* DMIC fix
+ * This laptop has a stereo digital microphone.
+ * The mics are only 1cm apart which makes the stereo
+ * useless. However, either the mic or the ALC889
+ * makes the signal become a difference/sum signal
+ * instead of standard stereo, which is annoying.
+ * So instead we flip this bit which makes the
+ * codec replicate the sum signal to both channels,
+ * turning it into a normal mono mic.
+ */
+ /* DMIC_CONTROL? Init value = 0x0001 */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x0b },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0003 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC882_FIXUP_GPIO1,
+ },
+ [ALC885_FIXUP_MACPRO_GPIO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc885_fixup_macpro_gpio,
+ },
+ [ALC889_FIXUP_DAC_ROUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_dac_route,
+ },
+ [ALC889_FIXUP_MBP_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_mbp_vref,
+ .chained = true,
+ .chain_id = ALC882_FIXUP_GPIO1,
+ },
+ [ALC889_FIXUP_IMAC91_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_imac91_vref,
+ .chained = true,
+ .chain_id = ALC882_FIXUP_GPIO1,
+ },
+ [ALC889_FIXUP_MBA11_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_mba11_vref,
+ .chained = true,
+ .chain_id = ALC889_FIXUP_MBP_VREF,
+ },
+ [ALC889_FIXUP_MBA21_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_mba21_vref,
+ .chained = true,
+ .chain_id = ALC889_FIXUP_MBP_VREF,
+ },
+ [ALC889_FIXUP_MP11_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_mba11_vref,
+ .chained = true,
+ .chain_id = ALC885_FIXUP_MACPRO_GPIO,
+ },
+ [ALC889_FIXUP_MP41_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc889_fixup_mbp_vref,
+ .chained = true,
+ .chain_id = ALC885_FIXUP_MACPRO_GPIO,
+ },
+ [ALC882_FIXUP_INV_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ },
+ [ALC882_FIXUP_NO_PRIMARY_HP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc882_fixup_no_primary_hp,
+ },
+ [ALC887_FIXUP_ASUS_BASS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x16, 0x99130130}, /* bass speaker */
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC887_FIXUP_BASS_CHMAP,
+ },
+ [ALC887_FIXUP_BASS_CHMAP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_bass_chmap,
+ },
+ [ALC1220_FIXUP_GB_DUAL_CODECS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc1220_fixup_gb_dual_codecs,
+ },
+ [ALC1220_FIXUP_GB_X570] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc1220_fixup_gb_x570,
+ },
+ [ALC1220_FIXUP_CLEVO_P950] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc1220_fixup_clevo_p950,
+ },
+ [ALC1220_FIXUP_CLEVO_PB51ED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc1220_fixup_clevo_pb51ed,
+ },
+ [ALC1220_FIXUP_CLEVO_PB51ED_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC1220_FIXUP_CLEVO_PB51ED,
+ },
+ [ALC887_FIXUP_ASUS_AUDIO] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x02a14150 }, /* use as headset mic, without its own jack detect */
+ { 0x19, 0x22219420 },
+ {}
+ },
+ },
+ [ALC887_FIXUP_ASUS_HMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc887_fixup_asus_jack,
+ .chained = true,
+ .chain_id = ALC887_FIXUP_ASUS_AUDIO,
+ },
+ [ALCS1200A_FIXUP_MIC_VREF] = {
+ .type = HDA_FIXUP_PINCTLS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, PIN_VREF50 }, /* rear mic */
+ { 0x19, PIN_VREF50 }, /* front mic */
+ {}
+ }
+ },
+ [ALC888VD_FIXUP_MIC_100VREF] = {
+ .type = HDA_FIXUP_PINCTLS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, PIN_VREF100 }, /* headset mic */
+ {}
+ }
+ },
+};
+
+static const struct snd_pci_quirk alc882_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1025, 0x006c, "Acer Aspire 9810", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x0090, "Acer Aspire", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x0107, "Acer Aspire", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x010a, "Acer Ferrari 5000", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x0110, "Acer Aspire", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x0112, "Acer Aspire 9303", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x0121, "Acer Aspire 5920G", ALC883_FIXUP_ACER_EAPD),
+ SND_PCI_QUIRK(0x1025, 0x013e, "Acer Aspire 4930G",
+ ALC882_FIXUP_ACER_ASPIRE_4930G),
+ SND_PCI_QUIRK(0x1025, 0x013f, "Acer Aspire 5930G",
+ ALC882_FIXUP_ACER_ASPIRE_4930G),
+ SND_PCI_QUIRK(0x1025, 0x0145, "Acer Aspire 8930G",
+ ALC882_FIXUP_ACER_ASPIRE_8930G),
+ SND_PCI_QUIRK(0x1025, 0x0146, "Acer Aspire 6935G",
+ ALC882_FIXUP_ACER_ASPIRE_8930G),
+ SND_PCI_QUIRK(0x1025, 0x0142, "Acer Aspire 7730G",
+ ALC882_FIXUP_ACER_ASPIRE_4930G),
+ SND_PCI_QUIRK(0x1025, 0x0155, "Packard-Bell M5120", ALC882_FIXUP_PB_M5210),
+ SND_PCI_QUIRK(0x1025, 0x015e, "Acer Aspire 6930G",
+ ALC882_FIXUP_ACER_ASPIRE_4930G),
+ SND_PCI_QUIRK(0x1025, 0x0166, "Acer Aspire 6530G",
+ ALC882_FIXUP_ACER_ASPIRE_4930G),
+ SND_PCI_QUIRK(0x1025, 0x021e, "Acer Aspire 5739G",
+ ALC882_FIXUP_ACER_ASPIRE_4930G),
+ SND_PCI_QUIRK(0x1025, 0x0259, "Acer Aspire 5935", ALC889_FIXUP_DAC_ROUTE),
+ SND_PCI_QUIRK(0x1025, 0x026b, "Acer Aspire 8940G", ALC882_FIXUP_ACER_ASPIRE_8930G),
+ SND_PCI_QUIRK(0x1025, 0x0296, "Acer Aspire 7736z", ALC882_FIXUP_ACER_ASPIRE_7736),
+ SND_PCI_QUIRK(0x1043, 0x13c2, "Asus A7M", ALC882_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x1043, 0x1873, "ASUS W90V", ALC882_FIXUP_ASUS_W90V),
+ SND_PCI_QUIRK(0x1043, 0x1971, "Asus W2JC", ALC882_FIXUP_ASUS_W2JC),
+ SND_PCI_QUIRK(0x1043, 0x2390, "Asus D700SA", ALC887_FIXUP_ASUS_HMIC),
+ SND_PCI_QUIRK(0x1043, 0x835f, "Asus Eee 1601", ALC888_FIXUP_EEE1601),
+ SND_PCI_QUIRK(0x1043, 0x84bc, "ASUS ET2700", ALC887_FIXUP_ASUS_BASS),
+ SND_PCI_QUIRK(0x1043, 0x8691, "ASUS ROG Ranger VIII", ALC882_FIXUP_GPIO3),
+ SND_PCI_QUIRK(0x1043, 0x8797, "ASUS TUF B550M-PLUS", ALCS1200A_FIXUP_MIC_VREF),
+ SND_PCI_QUIRK(0x104d, 0x9043, "Sony Vaio VGC-LN51JGB", ALC882_FIXUP_NO_PRIMARY_HP),
+ SND_PCI_QUIRK(0x104d, 0x9044, "Sony VAIO AiO", ALC882_FIXUP_NO_PRIMARY_HP),
+ SND_PCI_QUIRK(0x104d, 0x9047, "Sony Vaio TT", ALC889_FIXUP_VAIO_TT),
+ SND_PCI_QUIRK(0x104d, 0x905a, "Sony Vaio Z", ALC882_FIXUP_NO_PRIMARY_HP),
+ SND_PCI_QUIRK(0x104d, 0x9060, "Sony Vaio VPCL14M1R", ALC882_FIXUP_NO_PRIMARY_HP),
+
+ /* All Apple entries are in codec SSIDs */
+ SND_PCI_QUIRK(0x106b, 0x00a0, "MacBookPro 3,1", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x00a1, "Macbook", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x00a4, "MacbookPro 4,1", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x0c00, "Mac Pro", ALC889_FIXUP_MP11_VREF),
+ SND_PCI_QUIRK(0x106b, 0x1000, "iMac 24", ALC885_FIXUP_MACPRO_GPIO),
+ SND_PCI_QUIRK(0x106b, 0x2800, "AppleTV", ALC885_FIXUP_MACPRO_GPIO),
+ SND_PCI_QUIRK(0x106b, 0x2c00, "MacbookPro rev3", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x3000, "iMac", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x3200, "iMac 7,1 Aluminum", ALC882_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x106b, 0x3400, "MacBookAir 1,1", ALC889_FIXUP_MBA11_VREF),
+ SND_PCI_QUIRK(0x106b, 0x3500, "MacBookAir 2,1", ALC889_FIXUP_MBA21_VREF),
+ SND_PCI_QUIRK(0x106b, 0x3600, "Macbook 3,1", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x3800, "MacbookPro 4,1", ALC889_FIXUP_MBP_VREF),
+ SND_PCI_QUIRK(0x106b, 0x3e00, "iMac 24 Aluminum", ALC885_FIXUP_MACPRO_GPIO),
+ SND_PCI_QUIRK(0x106b, 0x3f00, "Macbook 5,1", ALC889_FIXUP_IMAC91_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4000, "MacbookPro 5,1", ALC889_FIXUP_IMAC91_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4100, "Macmini 3,1", ALC889_FIXUP_IMAC91_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4200, "Mac Pro 4,1/5,1", ALC889_FIXUP_MP41_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4300, "iMac 9,1", ALC889_FIXUP_IMAC91_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4600, "MacbookPro 5,2", ALC889_FIXUP_IMAC91_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4900, "iMac 9,1 Aluminum", ALC889_FIXUP_IMAC91_VREF),
+ SND_PCI_QUIRK(0x106b, 0x4a00, "Macbook 5,2", ALC889_FIXUP_MBA11_VREF),
+
+ SND_PCI_QUIRK(0x1071, 0x8258, "Evesham Voyaeger", ALC882_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x10ec, 0x12d8, "iBase Elo Touch", ALC888VD_FIXUP_MIC_100VREF),
+ SND_PCI_QUIRK(0x13fe, 0x1009, "Advantech MIT-W101", ALC886_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1458, 0xa0cd, "Gigabyte X570 Aorus Master", ALC1220_FIXUP_GB_X570),
+ SND_PCI_QUIRK(0x1458, 0xa0ce, "Gigabyte X570 Aorus Xtreme", ALC1220_FIXUP_GB_X570),
+ SND_PCI_QUIRK(0x1458, 0xa0d5, "Gigabyte X570S Aorus Master", ALC1220_FIXUP_GB_X570),
+ SND_PCI_QUIRK(0x1462, 0x11f7, "MSI-GE63", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1462, 0x1228, "MSI-GP63", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1462, 0x1229, "MSI-GP73", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1462, 0x1275, "MSI-GL63", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1462, 0x1276, "MSI-GL73", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1462, 0x1293, "MSI-GP65", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD),
+ SND_PCI_QUIRK(0x1462, 0xcc34, "MSI Godlike X570", ALC1220_FIXUP_GB_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1462, 0xda57, "MSI Z270-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS),
+ SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3),
+ SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX),
+ SND_PCI_QUIRK(0x1558, 0x3702, "Clevo X370SN[VW]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x50d3, "Clevo PC50[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x65d1, "Clevo PB51[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x65d2, "Clevo PB51R[CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x65e1, "Clevo PB51[ED][DF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x65e5, "Clevo PC50D[PRS](?:-D|-G)?", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x65f1, "Clevo PC50HS", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x65f5, "Clevo PD50PN[NRT]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x67d1, "Clevo PB71[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x67e1, "Clevo PB71[DE][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x67e5, "Clevo PC70D[PRS](?:-D|-G)?", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x67f1, "Clevo PC70H[PRS]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x67f5, "Clevo PD70PN[NRT]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x70d1, "Clevo PC70[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x7714, "Clevo X170SM", ALC1220_FIXUP_CLEVO_PB51ED_PINS),
+ SND_PCI_QUIRK(0x1558, 0x7715, "Clevo X170KM-G", ALC1220_FIXUP_CLEVO_PB51ED),
+ SND_PCI_QUIRK(0x1558, 0x9501, "Clevo P950HR", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x9506, "Clevo P955HQ", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x950a, "Clevo P955H[PR]", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x95e1, "Clevo P95xER", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x95e2, "Clevo P950ER", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x95e3, "Clevo P955[ER]T", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x95e4, "Clevo P955ER", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x95e5, "Clevo P955EE6", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x95e6, "Clevo P950R[CDF]", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x96e1, "Clevo P960[ER][CDFN]-K", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x97e1, "Clevo P970[ER][CDFN]", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK(0x1558, 0x97e2, "Clevo P970RC-M", ALC1220_FIXUP_CLEVO_P950),
+ SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD),
+ SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Y530", ALC882_FIXUP_LENOVO_Y530),
+ SND_PCI_QUIRK(0x8086, 0x0022, "DX58SO", ALC889_FIXUP_COEF),
+ {}
+};
+
+static const struct hda_model_fixup alc882_fixup_models[] = {
+ {.id = ALC882_FIXUP_ABIT_AW9D_MAX, .name = "abit-aw9d"},
+ {.id = ALC882_FIXUP_LENOVO_Y530, .name = "lenovo-y530"},
+ {.id = ALC882_FIXUP_ACER_ASPIRE_7736, .name = "acer-aspire-7736"},
+ {.id = ALC882_FIXUP_ASUS_W90V, .name = "asus-w90v"},
+ {.id = ALC889_FIXUP_CD, .name = "cd"},
+ {.id = ALC889_FIXUP_FRONT_HP_NO_PRESENCE, .name = "no-front-hp"},
+ {.id = ALC889_FIXUP_VAIO_TT, .name = "vaio-tt"},
+ {.id = ALC888_FIXUP_EEE1601, .name = "eee1601"},
+ {.id = ALC882_FIXUP_EAPD, .name = "alc882-eapd"},
+ {.id = ALC883_FIXUP_EAPD, .name = "alc883-eapd"},
+ {.id = ALC882_FIXUP_GPIO1, .name = "gpio1"},
+ {.id = ALC882_FIXUP_GPIO2, .name = "gpio2"},
+ {.id = ALC882_FIXUP_GPIO3, .name = "gpio3"},
+ {.id = ALC889_FIXUP_COEF, .name = "alc889-coef"},
+ {.id = ALC882_FIXUP_ASUS_W2JC, .name = "asus-w2jc"},
+ {.id = ALC882_FIXUP_ACER_ASPIRE_4930G, .name = "acer-aspire-4930g"},
+ {.id = ALC882_FIXUP_ACER_ASPIRE_8930G, .name = "acer-aspire-8930g"},
+ {.id = ALC883_FIXUP_ACER_EAPD, .name = "acer-aspire"},
+ {.id = ALC885_FIXUP_MACPRO_GPIO, .name = "macpro-gpio"},
+ {.id = ALC889_FIXUP_DAC_ROUTE, .name = "dac-route"},
+ {.id = ALC889_FIXUP_MBP_VREF, .name = "mbp-vref"},
+ {.id = ALC889_FIXUP_IMAC91_VREF, .name = "imac91-vref"},
+ {.id = ALC889_FIXUP_MBA11_VREF, .name = "mba11-vref"},
+ {.id = ALC889_FIXUP_MBA21_VREF, .name = "mba21-vref"},
+ {.id = ALC889_FIXUP_MP11_VREF, .name = "mp11-vref"},
+ {.id = ALC889_FIXUP_MP41_VREF, .name = "mp41-vref"},
+ {.id = ALC882_FIXUP_INV_DMIC, .name = "inv-dmic"},
+ {.id = ALC882_FIXUP_NO_PRIMARY_HP, .name = "no-primary-hp"},
+ {.id = ALC887_FIXUP_ASUS_BASS, .name = "asus-bass"},
+ {.id = ALC1220_FIXUP_GB_DUAL_CODECS, .name = "dual-codecs"},
+ {.id = ALC1220_FIXUP_GB_X570, .name = "gb-x570"},
+ {.id = ALC1220_FIXUP_CLEVO_P950, .name = "clevo-p950"},
+ {}
+};
+
+static const struct snd_hda_pin_quirk alc882_pin_fixup_tbl[] = {
+ SND_HDA_PIN_QUIRK(0x10ec1220, 0x1043, "ASUS", ALC1220_FIXUP_CLEVO_P950,
+ {0x14, 0x01014010},
+ {0x15, 0x01011012},
+ {0x16, 0x01016011},
+ {0x18, 0x01a19040},
+ {0x19, 0x02a19050},
+ {0x1a, 0x0181304f},
+ {0x1b, 0x0221401f},
+ {0x1e, 0x01456130}),
+ SND_HDA_PIN_QUIRK(0x10ec1220, 0x1462, "MS-7C35", ALC1220_FIXUP_CLEVO_P950,
+ {0x14, 0x01015010},
+ {0x15, 0x01011012},
+ {0x16, 0x01011011},
+ {0x18, 0x01a11040},
+ {0x19, 0x02a19050},
+ {0x1a, 0x0181104f},
+ {0x1b, 0x0221401f},
+ {0x1e, 0x01451130}),
+ {}
+};
+
+/*
+ * BIOS auto configuration
+ */
+/* almost identical with ALC880 parser... */
+static int alc882_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc882_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc882_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ return alc_parse_auto_config(codec, alc882_ignore, alc882_ssids);
+}
+
+/*
+ */
+static int patch_alc882(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x0b);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0882:
+ case 0x10ec0885:
+ case 0x10ec0900:
+ case 0x10ec0b00:
+ case 0x10ec1220:
+ break;
+ default:
+ /* ALC883 and variants */
+ alc_fix_pll_init(codec, 0x20, 0x0a, 10);
+ break;
+ }
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc882_fixup_models, alc882_fixup_tbl,
+ alc882_fixups);
+ snd_hda_pick_pin_fixup(codec, alc882_pin_fixup_tbl, alc882_fixups, true);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ alc_auto_parse_customize_define(codec);
+
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x01;
+
+ /* automatic parse from the BIOS config */
+ err = alc882_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog && spec->gen.beep_nid) {
+ err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+
+/*
+ * ALC262 support
+ */
+static int alc262_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc262_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc262_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ return alc_parse_auto_config(codec, alc262_ignore, alc262_ssids);
+}
+
+/*
+ * Pin config fixes
+ */
+enum {
+ ALC262_FIXUP_FSC_H270,
+ ALC262_FIXUP_FSC_S7110,
+ ALC262_FIXUP_HP_Z200,
+ ALC262_FIXUP_TYAN,
+ ALC262_FIXUP_LENOVO_3000,
+ ALC262_FIXUP_BENQ,
+ ALC262_FIXUP_BENQ_T31,
+ ALC262_FIXUP_INV_DMIC,
+ ALC262_FIXUP_INTEL_BAYLEYBAY,
+};
+
+static const struct hda_fixup alc262_fixups[] = {
+ [ALC262_FIXUP_FSC_H270] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x0221142f }, /* front HP */
+ { 0x1b, 0x0121141f }, /* rear HP */
+ { }
+ }
+ },
+ [ALC262_FIXUP_FSC_S7110] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x90170110 }, /* speaker */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC262_FIXUP_BENQ,
+ },
+ [ALC262_FIXUP_HP_Z200] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x99130120 }, /* internal speaker */
+ { }
+ }
+ },
+ [ALC262_FIXUP_TYAN] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x1993e1f0 }, /* int AUX */
+ { }
+ }
+ },
+ [ALC262_FIXUP_LENOVO_3000] = {
+ .type = HDA_FIXUP_PINCTLS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, PIN_VREF50 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC262_FIXUP_BENQ,
+ },
+ [ALC262_FIXUP_BENQ] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3070 },
+ {}
+ }
+ },
+ [ALC262_FIXUP_BENQ_T31] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 },
+ {}
+ }
+ },
+ [ALC262_FIXUP_INV_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ },
+ [ALC262_FIXUP_INTEL_BAYLEYBAY] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_no_depop_delay,
+ },
+};
+
+static const struct snd_pci_quirk alc262_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x103c, 0x170b, "HP Z200", ALC262_FIXUP_HP_Z200),
+ SND_PCI_QUIRK(0x10cf, 0x1397, "Fujitsu Lifebook S7110", ALC262_FIXUP_FSC_S7110),
+ SND_PCI_QUIRK(0x10cf, 0x142d, "Fujitsu Lifebook E8410", ALC262_FIXUP_BENQ),
+ SND_PCI_QUIRK(0x10f1, 0x2915, "Tyan Thunder n6650W", ALC262_FIXUP_TYAN),
+ SND_PCI_QUIRK(0x1734, 0x1141, "FSC ESPRIMO U9210", ALC262_FIXUP_FSC_H270),
+ SND_PCI_QUIRK(0x1734, 0x1147, "FSC Celsius H270", ALC262_FIXUP_FSC_H270),
+ SND_PCI_QUIRK(0x17aa, 0x384e, "Lenovo 3000", ALC262_FIXUP_LENOVO_3000),
+ SND_PCI_QUIRK(0x17ff, 0x0560, "Benq ED8", ALC262_FIXUP_BENQ),
+ SND_PCI_QUIRK(0x17ff, 0x058d, "Benq T31-16", ALC262_FIXUP_BENQ_T31),
+ SND_PCI_QUIRK(0x8086, 0x7270, "BayleyBay", ALC262_FIXUP_INTEL_BAYLEYBAY),
+ {}
+};
+
+static const struct hda_model_fixup alc262_fixup_models[] = {
+ {.id = ALC262_FIXUP_INV_DMIC, .name = "inv-dmic"},
+ {.id = ALC262_FIXUP_FSC_H270, .name = "fsc-h270"},
+ {.id = ALC262_FIXUP_FSC_S7110, .name = "fsc-s7110"},
+ {.id = ALC262_FIXUP_HP_Z200, .name = "hp-z200"},
+ {.id = ALC262_FIXUP_TYAN, .name = "tyan"},
+ {.id = ALC262_FIXUP_LENOVO_3000, .name = "lenovo-3000"},
+ {.id = ALC262_FIXUP_BENQ, .name = "benq"},
+ {.id = ALC262_FIXUP_BENQ_T31, .name = "benq-t31"},
+ {.id = ALC262_FIXUP_INTEL_BAYLEYBAY, .name = "bayleybay"},
+ {}
+};
+
+/*
+ */
+static int patch_alc262(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x0b);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->gen.shared_mic_vref_pin = 0x18;
+
+ spec->shutup = alc_eapd_shutup;
+
+#if 0
+ /* pshou 07/11/05 set a zero PCM sample to DAC when FIFO is
+ * under-run
+ */
+ alc_update_coefex_idx(codec, 0x1a, 7, 0, 0x80);
+#endif
+ alc_fix_pll_init(codec, 0x20, 0x0a, 10);
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc262_fixup_models, alc262_fixup_tbl,
+ alc262_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ alc_auto_parse_customize_define(codec);
+
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x01;
+
+ /* automatic parse from the BIOS config */
+ err = alc262_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog && spec->gen.beep_nid) {
+ err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+/*
+ * ALC268
+ */
+/* bind Beep switches of both NID 0x0f and 0x10 */
+static int alc268_beep_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned long pval;
+ int err;
+
+ mutex_lock(&codec->control_mutex);
+ pval = kcontrol->private_value;
+ kcontrol->private_value = (pval & ~0xff) | 0x0f;
+ err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ if (err >= 0) {
+ kcontrol->private_value = (pval & ~0xff) | 0x10;
+ err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ }
+ kcontrol->private_value = pval;
+ mutex_unlock(&codec->control_mutex);
+ return err;
+}
+
+static const struct snd_kcontrol_new alc268_beep_mixer[] = {
+ HDA_CODEC_VOLUME("Beep Playback Volume", 0x1d, 0x0, HDA_INPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Beep Playback Switch",
+ .subdevice = HDA_SUBDEV_AMP_FLAG,
+ .info = snd_hda_mixer_amp_switch_info,
+ .get = snd_hda_mixer_amp_switch_get,
+ .put = alc268_beep_switch_put,
+ .private_value = HDA_COMPOSE_AMP_VAL(0x0f, 3, 1, HDA_INPUT)
+ },
+};
+
+/* set PCBEEP vol = 0, mute connections */
+static const struct hda_verb alc268_beep_init_verbs[] = {
+ {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+ {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+ { }
+};
+
+enum {
+ ALC268_FIXUP_INV_DMIC,
+ ALC268_FIXUP_HP_EAPD,
+ ALC268_FIXUP_SPDIF,
+};
+
+static const struct hda_fixup alc268_fixups[] = {
+ [ALC268_FIXUP_INV_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ },
+ [ALC268_FIXUP_HP_EAPD] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x15, AC_VERB_SET_EAPD_BTLENABLE, 0},
+ {}
+ }
+ },
+ [ALC268_FIXUP_SPDIF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1e, 0x014b1180 }, /* enable SPDIF out */
+ {}
+ }
+ },
+};
+
+static const struct hda_model_fixup alc268_fixup_models[] = {
+ {.id = ALC268_FIXUP_INV_DMIC, .name = "inv-dmic"},
+ {.id = ALC268_FIXUP_HP_EAPD, .name = "hp-eapd"},
+ {.id = ALC268_FIXUP_SPDIF, .name = "spdif"},
+ {}
+};
+
+static const struct snd_pci_quirk alc268_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1025, 0x0139, "Acer TravelMate 6293", ALC268_FIXUP_SPDIF),
+ SND_PCI_QUIRK(0x1025, 0x015b, "Acer AOA 150 (ZG5)", ALC268_FIXUP_INV_DMIC),
+ /* below is codec SSID since multiple Toshiba laptops have the
+ * same PCI SSID 1179:ff00
+ */
+ SND_PCI_QUIRK(0x1179, 0xff06, "Toshiba P200", ALC268_FIXUP_HP_EAPD),
+ {}
+};
+
+/*
+ * BIOS auto configuration
+ */
+static int alc268_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc268_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ return alc_parse_auto_config(codec, NULL, alc268_ssids);
+}
+
+/*
+ */
+static int patch_alc268(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int i, err;
+
+ /* ALC268 has no aa-loopback mixer */
+ err = alc_alloc_spec(codec, 0);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x01;
+
+ spec->shutup = alc_eapd_shutup;
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc268_fixup_models, alc268_fixup_tbl, alc268_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ /* automatic parse from the BIOS config */
+ err = alc268_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (err > 0 && !spec->gen.no_analog &&
+ spec->gen.autocfg.speaker_pins[0] != 0x1d) {
+ for (i = 0; i < ARRAY_SIZE(alc268_beep_mixer); i++) {
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL,
+ &alc268_beep_mixer[i])) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+ snd_hda_add_verbs(codec, alc268_beep_init_verbs);
+ if (!query_amp_caps(codec, 0x1d, HDA_INPUT))
+ /* override the amp caps for beep generator */
+ snd_hda_override_amp_caps(codec, 0x1d, HDA_INPUT,
+ (0x0c << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x0c << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x07 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+/*
+ * ALC269
+ */
+
+static const struct hda_pcm_stream alc269_44k_pcm_analog_playback = {
+ .rates = SNDRV_PCM_RATE_44100, /* fixed rate */
+};
+
+static const struct hda_pcm_stream alc269_44k_pcm_analog_capture = {
+ .rates = SNDRV_PCM_RATE_44100, /* fixed rate */
+};
+
+/* different alc269-variants */
+enum {
+ ALC269_TYPE_ALC269VA,
+ ALC269_TYPE_ALC269VB,
+ ALC269_TYPE_ALC269VC,
+ ALC269_TYPE_ALC269VD,
+ ALC269_TYPE_ALC280,
+ ALC269_TYPE_ALC282,
+ ALC269_TYPE_ALC283,
+ ALC269_TYPE_ALC284,
+ ALC269_TYPE_ALC293,
+ ALC269_TYPE_ALC286,
+ ALC269_TYPE_ALC298,
+ ALC269_TYPE_ALC255,
+ ALC269_TYPE_ALC256,
+ ALC269_TYPE_ALC257,
+ ALC269_TYPE_ALC215,
+ ALC269_TYPE_ALC225,
+ ALC269_TYPE_ALC294,
+ ALC269_TYPE_ALC300,
+ ALC269_TYPE_ALC623,
+ ALC269_TYPE_ALC700,
+};
+
+/*
+ * BIOS auto configuration
+ */
+static int alc269_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc269_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc269_ssids[] = { 0, 0x1b, 0x14, 0x21 };
+ static const hda_nid_t alc269va_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ struct alc_spec *spec = codec->spec;
+ const hda_nid_t *ssids;
+
+ switch (spec->codec_variant) {
+ case ALC269_TYPE_ALC269VA:
+ case ALC269_TYPE_ALC269VC:
+ case ALC269_TYPE_ALC280:
+ case ALC269_TYPE_ALC284:
+ case ALC269_TYPE_ALC293:
+ ssids = alc269va_ssids;
+ break;
+ case ALC269_TYPE_ALC269VB:
+ case ALC269_TYPE_ALC269VD:
+ case ALC269_TYPE_ALC282:
+ case ALC269_TYPE_ALC283:
+ case ALC269_TYPE_ALC286:
+ case ALC269_TYPE_ALC298:
+ case ALC269_TYPE_ALC255:
+ case ALC269_TYPE_ALC256:
+ case ALC269_TYPE_ALC257:
+ case ALC269_TYPE_ALC215:
+ case ALC269_TYPE_ALC225:
+ case ALC269_TYPE_ALC294:
+ case ALC269_TYPE_ALC300:
+ case ALC269_TYPE_ALC623:
+ case ALC269_TYPE_ALC700:
+ ssids = alc269_ssids;
+ break;
+ default:
+ ssids = alc269_ssids;
+ break;
+ }
+
+ return alc_parse_auto_config(codec, alc269_ignore, ssids);
+}
+
+static const struct hda_jack_keymap alc_headset_btn_keymap[] = {
+ { SND_JACK_BTN_0, KEY_PLAYPAUSE },
+ { SND_JACK_BTN_1, KEY_VOICECOMMAND },
+ { SND_JACK_BTN_2, KEY_VOLUMEUP },
+ { SND_JACK_BTN_3, KEY_VOLUMEDOWN },
+ {}
+};
+
+static void alc_headset_btn_callback(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ int report = 0;
+
+ if (jack->unsol_res & (7 << 13))
+ report |= SND_JACK_BTN_0;
+
+ if (jack->unsol_res & (1 << 16 | 3 << 8))
+ report |= SND_JACK_BTN_1;
+
+ /* Volume up key */
+ if (jack->unsol_res & (7 << 23))
+ report |= SND_JACK_BTN_2;
+
+ /* Volume down key */
+ if (jack->unsol_res & (7 << 10))
+ report |= SND_JACK_BTN_3;
+
+ jack->jack->button_state = report;
+}
+
+static void alc_disable_headset_jack_key(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (!spec->has_hs_key)
+ return;
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0287:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ alc_write_coef_idx(codec, 0x48, 0x0);
+ alc_update_coef_idx(codec, 0x49, 0x0045, 0x0);
+ alc_update_coef_idx(codec, 0x44, 0x0045 << 8, 0x0);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x10ec0257:
+ case 0x19e58326:
+ alc_write_coef_idx(codec, 0x48, 0x0);
+ alc_update_coef_idx(codec, 0x49, 0x0045, 0x0);
+ break;
+ }
+}
+
+static void alc_enable_headset_jack_key(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (!spec->has_hs_key)
+ return;
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0287:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ alc_write_coef_idx(codec, 0x48, 0xd011);
+ alc_update_coef_idx(codec, 0x49, 0x007f, 0x0045);
+ alc_update_coef_idx(codec, 0x44, 0x007f << 8, 0x0045 << 8);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x10ec0257:
+ case 0x19e58326:
+ alc_write_coef_idx(codec, 0x48, 0xd011);
+ alc_update_coef_idx(codec, 0x49, 0x007f, 0x0045);
+ break;
+ }
+}
+
+static void alc_fixup_headset_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->has_hs_key = 1;
+ snd_hda_jack_detect_enable_callback(codec, 0x55,
+ alc_headset_btn_callback);
+ snd_hda_jack_add_kctl(codec, 0x55, "Headset Jack", false,
+ SND_JACK_HEADSET, alc_headset_btn_keymap);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc_enable_headset_jack_key(codec);
+ break;
+ }
+}
+
+static void alc269vb_toggle_power_output(struct hda_codec *codec, int power_up)
+{
+ alc_update_coef_idx(codec, 0x04, 1 << 11, power_up ? (1 << 11) : 0);
+}
+
+static void alc269_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (spec->codec_variant == ALC269_TYPE_ALC269VB)
+ alc269vb_toggle_power_output(codec, 0);
+ if (spec->codec_variant == ALC269_TYPE_ALC269VB &&
+ (alc_get_coef0(codec) & 0x00ff) == 0x018) {
+ msleep(150);
+ }
+ alc_shutup_pins(codec);
+}
+
+static const struct coef_fw alc282_coefs[] = {
+ WRITE_COEF(0x03, 0x0002), /* Power Down Control */
+ UPDATE_COEF(0x05, 0xff3f, 0x0700), /* FIFO and filter clock */
+ WRITE_COEF(0x07, 0x0200), /* DMIC control */
+ UPDATE_COEF(0x06, 0x00f0, 0), /* Analog clock */
+ UPDATE_COEF(0x08, 0xfffc, 0x0c2c), /* JD */
+ WRITE_COEF(0x0a, 0xcccc), /* JD offset1 */
+ WRITE_COEF(0x0b, 0xcccc), /* JD offset2 */
+ WRITE_COEF(0x0e, 0x6e00), /* LDO1/2/3, DAC/ADC */
+ UPDATE_COEF(0x0f, 0xf800, 0x1000), /* JD */
+ UPDATE_COEF(0x10, 0xfc00, 0x0c00), /* Capless */
+ WRITE_COEF(0x6f, 0x0), /* Class D test 4 */
+ UPDATE_COEF(0x0c, 0xfe00, 0), /* IO power down directly */
+ WRITE_COEF(0x34, 0xa0c0), /* ANC */
+ UPDATE_COEF(0x16, 0x0008, 0), /* AGC MUX */
+ UPDATE_COEF(0x1d, 0x00e0, 0), /* DAC simple content protection */
+ UPDATE_COEF(0x1f, 0x00e0, 0), /* ADC simple content protection */
+ WRITE_COEF(0x21, 0x8804), /* DAC ADC Zero Detection */
+ WRITE_COEF(0x63, 0x2902), /* PLL */
+ WRITE_COEF(0x68, 0xa080), /* capless control 2 */
+ WRITE_COEF(0x69, 0x3400), /* capless control 3 */
+ WRITE_COEF(0x6a, 0x2f3e), /* capless control 4 */
+ WRITE_COEF(0x6b, 0x0), /* capless control 5 */
+ UPDATE_COEF(0x6d, 0x0fff, 0x0900), /* class D test 2 */
+ WRITE_COEF(0x6e, 0x110a), /* class D test 3 */
+ UPDATE_COEF(0x70, 0x00f8, 0x00d8), /* class D test 5 */
+ WRITE_COEF(0x71, 0x0014), /* class D test 6 */
+ WRITE_COEF(0x72, 0xc2ba), /* classD OCP */
+ UPDATE_COEF(0x77, 0x0f80, 0), /* classD pure DC test */
+ WRITE_COEF(0x6c, 0xfc06), /* Class D amp control */
+ {}
+};
+
+static void alc282_restore_default_value(struct hda_codec *codec)
+{
+ alc_process_coef_fw(codec, alc282_coefs);
+}
+
+static void alc282_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+ int coef78;
+
+ alc282_restore_default_value(codec);
+
+ if (!hp_pin)
+ return;
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+ coef78 = alc_read_coef_idx(codec, 0x78);
+
+ /* Index 0x78 Direct Drive HP AMP LPM Control 1 */
+ /* Headphone capless set to high power mode */
+ alc_write_coef_idx(codec, 0x78, 0x9004);
+
+ if (hp_pin_sense)
+ msleep(2);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense)
+ msleep(85);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+
+ if (hp_pin_sense)
+ msleep(100);
+
+ /* Headphone capless set to normal mode */
+ alc_write_coef_idx(codec, 0x78, coef78);
+}
+
+static void alc282_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+ int coef78;
+
+ if (!hp_pin) {
+ alc269_shutup(codec);
+ return;
+ }
+
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+ coef78 = alc_read_coef_idx(codec, 0x78);
+ alc_write_coef_idx(codec, 0x78, 0x9004);
+
+ if (hp_pin_sense)
+ msleep(2);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense)
+ msleep(85);
+
+ if (!spec->no_shutup_pins)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ if (hp_pin_sense)
+ msleep(100);
+
+ alc_auto_setup_eapd(codec, false);
+ alc_shutup_pins(codec);
+ alc_write_coef_idx(codec, 0x78, coef78);
+}
+
+static const struct coef_fw alc283_coefs[] = {
+ WRITE_COEF(0x03, 0x0002), /* Power Down Control */
+ UPDATE_COEF(0x05, 0xff3f, 0x0700), /* FIFO and filter clock */
+ WRITE_COEF(0x07, 0x0200), /* DMIC control */
+ UPDATE_COEF(0x06, 0x00f0, 0), /* Analog clock */
+ UPDATE_COEF(0x08, 0xfffc, 0x0c2c), /* JD */
+ WRITE_COEF(0x0a, 0xcccc), /* JD offset1 */
+ WRITE_COEF(0x0b, 0xcccc), /* JD offset2 */
+ WRITE_COEF(0x0e, 0x6fc0), /* LDO1/2/3, DAC/ADC */
+ UPDATE_COEF(0x0f, 0xf800, 0x1000), /* JD */
+ UPDATE_COEF(0x10, 0xfc00, 0x0c00), /* Capless */
+ WRITE_COEF(0x3a, 0x0), /* Class D test 4 */
+ UPDATE_COEF(0x0c, 0xfe00, 0x0), /* IO power down directly */
+ WRITE_COEF(0x22, 0xa0c0), /* ANC */
+ UPDATE_COEFEX(0x53, 0x01, 0x000f, 0x0008), /* AGC MUX */
+ UPDATE_COEF(0x1d, 0x00e0, 0), /* DAC simple content protection */
+ UPDATE_COEF(0x1f, 0x00e0, 0), /* ADC simple content protection */
+ WRITE_COEF(0x21, 0x8804), /* DAC ADC Zero Detection */
+ WRITE_COEF(0x2e, 0x2902), /* PLL */
+ WRITE_COEF(0x33, 0xa080), /* capless control 2 */
+ WRITE_COEF(0x34, 0x3400), /* capless control 3 */
+ WRITE_COEF(0x35, 0x2f3e), /* capless control 4 */
+ WRITE_COEF(0x36, 0x0), /* capless control 5 */
+ UPDATE_COEF(0x38, 0x0fff, 0x0900), /* class D test 2 */
+ WRITE_COEF(0x39, 0x110a), /* class D test 3 */
+ UPDATE_COEF(0x3b, 0x00f8, 0x00d8), /* class D test 5 */
+ WRITE_COEF(0x3c, 0x0014), /* class D test 6 */
+ WRITE_COEF(0x3d, 0xc2ba), /* classD OCP */
+ UPDATE_COEF(0x42, 0x0f80, 0x0), /* classD pure DC test */
+ WRITE_COEF(0x49, 0x0), /* test mode */
+ UPDATE_COEF(0x40, 0xf800, 0x9800), /* Class D DC enable */
+ UPDATE_COEF(0x42, 0xf000, 0x2000), /* DC offset */
+ WRITE_COEF(0x37, 0xfc06), /* Class D amp control */
+ UPDATE_COEF(0x1b, 0x8000, 0), /* HP JD control */
+ {}
+};
+
+static void alc283_restore_default_value(struct hda_codec *codec)
+{
+ alc_process_coef_fw(codec, alc283_coefs);
+}
+
+static void alc283_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+
+ alc283_restore_default_value(codec);
+
+ if (!hp_pin)
+ return;
+
+ msleep(30);
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+
+ /* Index 0x43 Direct Drive HP AMP LPM Control 1 */
+ /* Headphone capless set to high power mode */
+ alc_write_coef_idx(codec, 0x43, 0x9004);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense)
+ msleep(85);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+
+ if (hp_pin_sense)
+ msleep(85);
+ /* Index 0x46 Combo jack auto switch control 2 */
+ /* 3k pull low control for Headset jack. */
+ alc_update_coef_idx(codec, 0x46, 3 << 12, 0);
+ /* Headphone capless set to normal mode */
+ alc_write_coef_idx(codec, 0x43, 0x9614);
+}
+
+static void alc283_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+
+ if (!hp_pin) {
+ alc269_shutup(codec);
+ return;
+ }
+
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+
+ alc_write_coef_idx(codec, 0x43, 0x9004);
+
+ /*depop hp during suspend*/
+ alc_write_coef_idx(codec, 0x06, 0x2100);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense)
+ msleep(100);
+
+ if (!spec->no_shutup_pins)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ alc_update_coef_idx(codec, 0x46, 0, 3 << 12);
+
+ if (hp_pin_sense)
+ msleep(100);
+ alc_auto_setup_eapd(codec, false);
+ alc_shutup_pins(codec);
+ alc_write_coef_idx(codec, 0x43, 0x9614);
+}
+
+static void alc256_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+
+ if (spec->ultra_low_power) {
+ alc_update_coef_idx(codec, 0x03, 1<<1, 1<<1);
+ alc_update_coef_idx(codec, 0x08, 3<<2, 3<<2);
+ alc_update_coef_idx(codec, 0x08, 7<<4, 0);
+ alc_update_coef_idx(codec, 0x3b, 1<<15, 0);
+ alc_update_coef_idx(codec, 0x0e, 7<<6, 7<<6);
+ msleep(30);
+ }
+
+ if (!hp_pin)
+ hp_pin = 0x21;
+
+ msleep(30);
+
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+
+ if (hp_pin_sense)
+ msleep(2);
+
+ alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x1); /* Low power */
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense || spec->ultra_low_power)
+ msleep(85);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+
+ if (hp_pin_sense || spec->ultra_low_power)
+ msleep(100);
+
+ alc_update_coef_idx(codec, 0x46, 3 << 12, 0);
+ alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x4); /* Hight power */
+ alc_update_coefex_idx(codec, 0x53, 0x02, 0x8000, 1 << 15); /* Clear bit */
+ alc_update_coefex_idx(codec, 0x53, 0x02, 0x8000, 0 << 15);
+ /*
+ * Expose headphone mic (or possibly Line In on some machines) instead
+ * of PC Beep on 1Ah, and disable 1Ah loopback for all outputs. See
+ * Documentation/sound/hd-audio/realtek-pc-beep.rst for details of
+ * this register.
+ */
+ alc_write_coef_idx(codec, 0x36, 0x5757);
+}
+
+static void alc256_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+
+ if (!hp_pin)
+ hp_pin = 0x21;
+
+ alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x1); /* Low power */
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+
+ if (hp_pin_sense)
+ msleep(2);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense || spec->ultra_low_power)
+ msleep(85);
+
+ /* 3k pull low control for Headset jack. */
+ /* NOTE: call this before clearing the pin, otherwise codec stalls */
+ /* If disable 3k pulldown control for alc257, the Mic detection will not work correctly
+ * when booting with headset plugged. So skip setting it for the codec alc257
+ */
+ if (spec->en_3kpull_low)
+ alc_update_coef_idx(codec, 0x46, 0, 3 << 12);
+
+ if (!spec->no_shutup_pins)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ if (hp_pin_sense || spec->ultra_low_power)
+ msleep(100);
+
+ alc_auto_setup_eapd(codec, false);
+ alc_shutup_pins(codec);
+ if (spec->ultra_low_power) {
+ msleep(50);
+ alc_update_coef_idx(codec, 0x03, 1<<1, 0);
+ alc_update_coef_idx(codec, 0x08, 7<<4, 7<<4);
+ alc_update_coef_idx(codec, 0x08, 3<<2, 0);
+ alc_update_coef_idx(codec, 0x3b, 1<<15, 1<<15);
+ alc_update_coef_idx(codec, 0x0e, 7<<6, 0);
+ msleep(30);
+ }
+}
+
+static void alc225_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp1_pin_sense, hp2_pin_sense;
+
+ if (spec->ultra_low_power) {
+ alc_update_coef_idx(codec, 0x08, 0x0f << 2, 3<<2);
+ alc_update_coef_idx(codec, 0x0e, 7<<6, 7<<6);
+ alc_update_coef_idx(codec, 0x33, 1<<11, 0);
+ msleep(30);
+ }
+
+ if (!hp_pin)
+ hp_pin = 0x21;
+ msleep(30);
+
+ hp1_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+ hp2_pin_sense = snd_hda_jack_detect(codec, 0x16);
+
+ if (hp1_pin_sense || hp2_pin_sense)
+ msleep(2);
+
+ alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x1); /* Low power */
+
+ if (hp1_pin_sense || spec->ultra_low_power)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ if (hp2_pin_sense)
+ snd_hda_codec_write(codec, 0x16, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power)
+ msleep(85);
+
+ if (hp1_pin_sense || spec->ultra_low_power)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+ if (hp2_pin_sense)
+ snd_hda_codec_write(codec, 0x16, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+
+ if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power)
+ msleep(100);
+
+ alc_update_coef_idx(codec, 0x4a, 3 << 10, 0);
+ alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x4); /* Hight power */
+}
+
+static void alc225_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp1_pin_sense, hp2_pin_sense;
+
+ if (!hp_pin)
+ hp_pin = 0x21;
+
+ alc_disable_headset_jack_key(codec);
+ /* 3k pull low control for Headset jack. */
+ alc_update_coef_idx(codec, 0x4a, 0, 3 << 10);
+
+ hp1_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+ hp2_pin_sense = snd_hda_jack_detect(codec, 0x16);
+
+ if (hp1_pin_sense || hp2_pin_sense)
+ msleep(2);
+
+ if (hp1_pin_sense || spec->ultra_low_power)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ if (hp2_pin_sense)
+ snd_hda_codec_write(codec, 0x16, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power)
+ msleep(85);
+
+ if (hp1_pin_sense || spec->ultra_low_power)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+ if (hp2_pin_sense)
+ snd_hda_codec_write(codec, 0x16, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power)
+ msleep(100);
+
+ alc_auto_setup_eapd(codec, false);
+ alc_shutup_pins(codec);
+ if (spec->ultra_low_power) {
+ msleep(50);
+ alc_update_coef_idx(codec, 0x08, 0x0f << 2, 0x0c << 2);
+ alc_update_coef_idx(codec, 0x0e, 7<<6, 0);
+ alc_update_coef_idx(codec, 0x33, 1<<11, 1<<11);
+ alc_update_coef_idx(codec, 0x4a, 3<<4, 2<<4);
+ msleep(30);
+ }
+
+ alc_update_coef_idx(codec, 0x4a, 3 << 10, 0);
+ alc_enable_headset_jack_key(codec);
+}
+
+static void alc_default_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+
+ if (!hp_pin)
+ return;
+
+ msleep(30);
+
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+
+ if (hp_pin_sense)
+ msleep(2);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense)
+ msleep(85);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+
+ if (hp_pin_sense)
+ msleep(100);
+}
+
+static void alc_default_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ bool hp_pin_sense;
+
+ if (!hp_pin) {
+ alc269_shutup(codec);
+ return;
+ }
+
+ hp_pin_sense = snd_hda_jack_detect(codec, hp_pin);
+
+ if (hp_pin_sense)
+ msleep(2);
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ if (hp_pin_sense)
+ msleep(85);
+
+ if (!spec->no_shutup_pins)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ if (hp_pin_sense)
+ msleep(100);
+
+ alc_auto_setup_eapd(codec, false);
+ alc_shutup_pins(codec);
+}
+
+static void alc294_hp_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+ int i, val;
+
+ if (!hp_pin)
+ return;
+
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+ msleep(100);
+
+ if (!spec->no_shutup_pins)
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ alc_update_coef_idx(codec, 0x6f, 0x000f, 0);/* Set HP depop to manual mode */
+ alc_update_coefex_idx(codec, 0x58, 0x00, 0x8000, 0x8000); /* HP depop procedure start */
+
+ /* Wait for depop procedure finish */
+ val = alc_read_coefex_idx(codec, 0x58, 0x01);
+ for (i = 0; i < 20 && val & 0x0080; i++) {
+ msleep(50);
+ val = alc_read_coefex_idx(codec, 0x58, 0x01);
+ }
+ /* Set HP depop to auto mode */
+ alc_update_coef_idx(codec, 0x6f, 0x000f, 0x000b);
+ msleep(50);
+}
+
+static void alc294_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ /* required only at boot or S4 resume time */
+ if (!spec->done_hp_init ||
+ codec->core.dev.power.power_state.event == PM_EVENT_RESTORE) {
+ alc294_hp_init(codec);
+ spec->done_hp_init = true;
+ }
+ alc_default_init(codec);
+}
+
+static void alc5505_coef_set(struct hda_codec *codec, unsigned int index_reg,
+ unsigned int val)
+{
+ snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_COEF_INDEX, index_reg >> 1);
+ snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_PROC_COEF, val & 0xffff); /* LSB */
+ snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_PROC_COEF, val >> 16); /* MSB */
+}
+
+static int alc5505_coef_get(struct hda_codec *codec, unsigned int index_reg)
+{
+ unsigned int val;
+
+ snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_COEF_INDEX, index_reg >> 1);
+ val = snd_hda_codec_read(codec, 0x51, 0, AC_VERB_GET_PROC_COEF, 0)
+ & 0xffff;
+ val |= snd_hda_codec_read(codec, 0x51, 0, AC_VERB_GET_PROC_COEF, 0)
+ << 16;
+ return val;
+}
+
+static void alc5505_dsp_halt(struct hda_codec *codec)
+{
+ unsigned int val;
+
+ alc5505_coef_set(codec, 0x3000, 0x000c); /* DSP CPU stop */
+ alc5505_coef_set(codec, 0x880c, 0x0008); /* DDR enter self refresh */
+ alc5505_coef_set(codec, 0x61c0, 0x11110080); /* Clock control for PLL and CPU */
+ alc5505_coef_set(codec, 0x6230, 0xfc0d4011); /* Disable Input OP */
+ alc5505_coef_set(codec, 0x61b4, 0x040a2b03); /* Stop PLL2 */
+ alc5505_coef_set(codec, 0x61b0, 0x00005b17); /* Stop PLL1 */
+ alc5505_coef_set(codec, 0x61b8, 0x04133303); /* Stop PLL3 */
+ val = alc5505_coef_get(codec, 0x6220);
+ alc5505_coef_set(codec, 0x6220, (val | 0x3000)); /* switch Ringbuffer clock to DBUS clock */
+}
+
+static void alc5505_dsp_back_from_halt(struct hda_codec *codec)
+{
+ alc5505_coef_set(codec, 0x61b8, 0x04133302);
+ alc5505_coef_set(codec, 0x61b0, 0x00005b16);
+ alc5505_coef_set(codec, 0x61b4, 0x040a2b02);
+ alc5505_coef_set(codec, 0x6230, 0xf80d4011);
+ alc5505_coef_set(codec, 0x6220, 0x2002010f);
+ alc5505_coef_set(codec, 0x880c, 0x00000004);
+}
+
+static void alc5505_dsp_init(struct hda_codec *codec)
+{
+ unsigned int val;
+
+ alc5505_dsp_halt(codec);
+ alc5505_dsp_back_from_halt(codec);
+ alc5505_coef_set(codec, 0x61b0, 0x5b14); /* PLL1 control */
+ alc5505_coef_set(codec, 0x61b0, 0x5b16);
+ alc5505_coef_set(codec, 0x61b4, 0x04132b00); /* PLL2 control */
+ alc5505_coef_set(codec, 0x61b4, 0x04132b02);
+ alc5505_coef_set(codec, 0x61b8, 0x041f3300); /* PLL3 control*/
+ alc5505_coef_set(codec, 0x61b8, 0x041f3302);
+ snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_CODEC_RESET, 0); /* Function reset */
+ alc5505_coef_set(codec, 0x61b8, 0x041b3302);
+ alc5505_coef_set(codec, 0x61b8, 0x04173302);
+ alc5505_coef_set(codec, 0x61b8, 0x04163302);
+ alc5505_coef_set(codec, 0x8800, 0x348b328b); /* DRAM control */
+ alc5505_coef_set(codec, 0x8808, 0x00020022); /* DRAM control */
+ alc5505_coef_set(codec, 0x8818, 0x00000400); /* DRAM control */
+
+ val = alc5505_coef_get(codec, 0x6200) >> 16; /* Read revision ID */
+ if (val <= 3)
+ alc5505_coef_set(codec, 0x6220, 0x2002010f); /* I/O PAD Configuration */
+ else
+ alc5505_coef_set(codec, 0x6220, 0x6002018f);
+
+ alc5505_coef_set(codec, 0x61ac, 0x055525f0); /**/
+ alc5505_coef_set(codec, 0x61c0, 0x12230080); /* Clock control */
+ alc5505_coef_set(codec, 0x61b4, 0x040e2b02); /* PLL2 control */
+ alc5505_coef_set(codec, 0x61bc, 0x010234f8); /* OSC Control */
+ alc5505_coef_set(codec, 0x880c, 0x00000004); /* DRAM Function control */
+ alc5505_coef_set(codec, 0x880c, 0x00000003);
+ alc5505_coef_set(codec, 0x880c, 0x00000010);
+
+#ifdef HALT_REALTEK_ALC5505
+ alc5505_dsp_halt(codec);
+#endif
+}
+
+#ifdef HALT_REALTEK_ALC5505
+#define alc5505_dsp_suspend(codec) do { } while (0) /* NOP */
+#define alc5505_dsp_resume(codec) do { } while (0) /* NOP */
+#else
+#define alc5505_dsp_suspend(codec) alc5505_dsp_halt(codec)
+#define alc5505_dsp_resume(codec) alc5505_dsp_back_from_halt(codec)
+#endif
+
+#ifdef CONFIG_PM
+static int alc269_suspend(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (spec->has_alc5505_dsp)
+ alc5505_dsp_suspend(codec);
+ return alc_suspend(codec);
+}
+
+static int alc269_resume(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (spec->codec_variant == ALC269_TYPE_ALC269VB)
+ alc269vb_toggle_power_output(codec, 0);
+ if (spec->codec_variant == ALC269_TYPE_ALC269VB &&
+ (alc_get_coef0(codec) & 0x00ff) == 0x018) {
+ msleep(150);
+ }
+
+ codec->patch_ops.init(codec);
+
+ if (spec->codec_variant == ALC269_TYPE_ALC269VB)
+ alc269vb_toggle_power_output(codec, 1);
+ if (spec->codec_variant == ALC269_TYPE_ALC269VB &&
+ (alc_get_coef0(codec) & 0x00ff) == 0x017) {
+ msleep(200);
+ }
+
+ snd_hda_regmap_sync(codec);
+ hda_call_check_power_status(codec, 0x01);
+
+ /* on some machine, the BIOS will clear the codec gpio data when enter
+ * suspend, and won't restore the data after resume, so we restore it
+ * in the driver.
+ */
+ if (spec->gpio_data)
+ alc_write_gpio_data(codec);
+
+ if (spec->has_alc5505_dsp)
+ alc5505_dsp_resume(codec);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static void alc269_fixup_pincfg_no_hp_to_lineout(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP;
+}
+
+static void alc269_fixup_pincfg_U7x7_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ unsigned int cfg_headphone = snd_hda_codec_get_pincfg(codec, 0x21);
+ unsigned int cfg_headset_mic = snd_hda_codec_get_pincfg(codec, 0x19);
+
+ if (cfg_headphone && cfg_headset_mic == 0x411111f0)
+ snd_hda_codec_set_pincfg(codec, 0x19,
+ (cfg_headphone & ~AC_DEFCFG_DEVICE) |
+ (AC_JACK_MIC_IN << AC_DEFCFG_DEVICE_SHIFT));
+}
+
+static void alc269_fixup_hweq(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_INIT)
+ alc_update_coef_idx(codec, 0x1e, 0, 0x80);
+}
+
+static void alc269_fixup_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+}
+
+static void alc271_fixup_dmic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_verb verbs[] = {
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x0d},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x4000},
+ {}
+ };
+ unsigned int cfg;
+
+ if (strcmp(codec->core.chip_name, "ALC271X") &&
+ strcmp(codec->core.chip_name, "ALC269VB"))
+ return;
+ cfg = snd_hda_codec_get_pincfg(codec, 0x12);
+ if (get_defcfg_connect(cfg) == AC_JACK_PORT_FIXED)
+ snd_hda_sequence_write(codec, verbs);
+}
+
+/* Fix the speaker amp after resume, etc */
+static void alc269vb_fixup_aspire_e1_coef(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ if (action == HDA_FIXUP_ACT_INIT)
+ alc_update_coef_idx(codec, 0x0d, 0x6000, 0x6000);
+}
+
+static void alc269_fixup_pcm_44k(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PROBE)
+ return;
+
+ /* Due to a hardware problem on Lenovo Ideadpad, we need to
+ * fix the sample rate of analog I/O to 44.1kHz
+ */
+ spec->gen.stream_analog_playback = &alc269_44k_pcm_analog_playback;
+ spec->gen.stream_analog_capture = &alc269_44k_pcm_analog_capture;
+}
+
+static void alc269_fixup_stereo_dmic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ /* The digital-mic unit sends PDM (differential signal) instead of
+ * the standard PCM, thus you can't record a valid mono stream as is.
+ * Below is a workaround specific to ALC269 to control the dmic
+ * signal source as mono.
+ */
+ if (action == HDA_FIXUP_ACT_INIT)
+ alc_update_coef_idx(codec, 0x07, 0, 0x80);
+}
+
+static void alc269_quanta_automute(struct hda_codec *codec)
+{
+ snd_hda_gen_update_outputs(codec);
+
+ alc_write_coef_idx(codec, 0x0c, 0x680);
+ alc_write_coef_idx(codec, 0x0c, 0x480);
+}
+
+static void alc269_fixup_quanta_mute(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action != HDA_FIXUP_ACT_PROBE)
+ return;
+ spec->gen.automute_hook = alc269_quanta_automute;
+}
+
+static void alc269_x101_hp_automute_hook(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+ int vref;
+ msleep(200);
+ snd_hda_gen_hp_automute(codec, jack);
+
+ vref = spec->gen.hp_jack_present ? PIN_VREF80 : 0;
+ msleep(100);
+ snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+ vref);
+ msleep(500);
+ snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+ vref);
+}
+
+/*
+ * Magic sequence to make Huawei Matebook X right speaker working (bko#197801)
+ */
+struct hda_alc298_mbxinit {
+ unsigned char value_0x23;
+ unsigned char value_0x25;
+};
+
+static void alc298_huawei_mbx_stereo_seq(struct hda_codec *codec,
+ const struct hda_alc298_mbxinit *initval,
+ bool first)
+{
+ snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x0);
+ alc_write_coef_idx(codec, 0x26, 0xb000);
+
+ if (first)
+ snd_hda_codec_write(codec, 0x21, 0, AC_VERB_GET_PIN_SENSE, 0x0);
+
+ snd_hda_codec_write(codec, 0x6, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x80);
+ alc_write_coef_idx(codec, 0x26, 0xf000);
+ alc_write_coef_idx(codec, 0x23, initval->value_0x23);
+
+ if (initval->value_0x23 != 0x1e)
+ alc_write_coef_idx(codec, 0x25, initval->value_0x25);
+
+ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x26);
+ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, 0xb010);
+}
+
+static void alc298_fixup_huawei_mbx_stereo(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ /* Initialization magic */
+ static const struct hda_alc298_mbxinit dac_init[] = {
+ {0x0c, 0x00}, {0x0d, 0x00}, {0x0e, 0x00}, {0x0f, 0x00},
+ {0x10, 0x00}, {0x1a, 0x40}, {0x1b, 0x82}, {0x1c, 0x00},
+ {0x1d, 0x00}, {0x1e, 0x00}, {0x1f, 0x00},
+ {0x20, 0xc2}, {0x21, 0xc8}, {0x22, 0x26}, {0x23, 0x24},
+ {0x27, 0xff}, {0x28, 0xff}, {0x29, 0xff}, {0x2a, 0x8f},
+ {0x2b, 0x02}, {0x2c, 0x48}, {0x2d, 0x34}, {0x2e, 0x00},
+ {0x2f, 0x00},
+ {0x30, 0x00}, {0x31, 0x00}, {0x32, 0x00}, {0x33, 0x00},
+ {0x34, 0x00}, {0x35, 0x01}, {0x36, 0x93}, {0x37, 0x0c},
+ {0x38, 0x00}, {0x39, 0x00}, {0x3a, 0xf8}, {0x38, 0x80},
+ {}
+ };
+ const struct hda_alc298_mbxinit *seq;
+
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+
+ /* Start */
+ snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x00);
+ snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x80);
+ alc_write_coef_idx(codec, 0x26, 0xf000);
+ alc_write_coef_idx(codec, 0x22, 0x31);
+ alc_write_coef_idx(codec, 0x23, 0x0b);
+ alc_write_coef_idx(codec, 0x25, 0x00);
+ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x26);
+ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, 0xb010);
+
+ for (seq = dac_init; seq->value_0x23; seq++)
+ alc298_huawei_mbx_stereo_seq(codec, seq, seq == dac_init);
+}
+
+static void alc269_fixup_x101_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ spec->gen.hp_automute_hook = alc269_x101_hp_automute_hook;
+ }
+}
+
+static void alc_update_vref_led(struct hda_codec *codec, hda_nid_t pin,
+ bool polarity, bool on)
+{
+ unsigned int pinval;
+
+ if (!pin)
+ return;
+ if (polarity)
+ on = !on;
+ pinval = snd_hda_codec_get_pin_target(codec, pin);
+ pinval &= ~AC_PINCTL_VREFEN;
+ pinval |= on ? AC_PINCTL_VREF_80 : AC_PINCTL_VREF_HIZ;
+ /* temporarily power up/down for setting VREF */
+ snd_hda_power_up_pm(codec);
+ snd_hda_set_pin_ctl_cache(codec, pin, pinval);
+ snd_hda_power_down_pm(codec);
+}
+
+/* update mute-LED according to the speaker mute state via mic VREF pin */
+static int vref_mute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_vref_led(codec, spec->mute_led_nid,
+ spec->mute_led_polarity, brightness);
+ return 0;
+}
+
+/* Make sure the led works even in runtime suspend */
+static unsigned int led_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (power_state != AC_PWRST_D3 || nid == 0 ||
+ (nid != spec->mute_led_nid && nid != spec->cap_mute_led_nid))
+ return power_state;
+
+ /* Set pin ctl again, it might have just been set to 0 */
+ snd_hda_set_pin_ctl(codec, nid,
+ snd_hda_codec_get_pin_target(codec, nid));
+
+ return snd_hda_gen_path_power_filter(codec, nid, power_state);
+}
+
+static void alc269_fixup_hp_mute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ const struct dmi_device *dev = NULL;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+ int pol, pin;
+ if (sscanf(dev->name, "HP_Mute_LED_%d_%x", &pol, &pin) != 2)
+ continue;
+ if (pin < 0x0a || pin >= 0x10)
+ break;
+ spec->mute_led_polarity = pol;
+ spec->mute_led_nid = pin - 0x0a + 0x18;
+ snd_hda_gen_add_mute_led_cdev(codec, vref_mute_led_set);
+ codec->power_filter = led_power_filter;
+ codec_dbg(codec,
+ "Detected mute LED for %x:%d\n", spec->mute_led_nid,
+ spec->mute_led_polarity);
+ break;
+ }
+}
+
+static void alc269_fixup_hp_mute_led_micx(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action, hda_nid_t pin)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mute_led_polarity = 0;
+ spec->mute_led_nid = pin;
+ snd_hda_gen_add_mute_led_cdev(codec, vref_mute_led_set);
+ codec->power_filter = led_power_filter;
+ }
+}
+
+static void alc269_fixup_hp_mute_led_mic1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x18);
+}
+
+static void alc269_fixup_hp_mute_led_mic2(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x19);
+}
+
+static void alc269_fixup_hp_mute_led_mic3(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x1b);
+}
+
+/* update LED status via GPIO */
+static void alc_update_gpio_led(struct hda_codec *codec, unsigned int mask,
+ int polarity, bool enabled)
+{
+ if (polarity)
+ enabled = !enabled;
+ alc_update_gpio_data(codec, mask, !enabled); /* muted -> LED on */
+}
+
+/* turn on/off mute LED via GPIO per vmaster hook */
+static int gpio_mute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_gpio_led(codec, spec->gpio_mute_led_mask,
+ spec->mute_led_polarity, !brightness);
+ return 0;
+}
+
+/* turn on/off mic-mute LED via GPIO per capture hook */
+static int micmute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_gpio_led(codec, spec->gpio_mic_led_mask,
+ spec->micmute_led_polarity, !brightness);
+ return 0;
+}
+
+/* setup mute and mic-mute GPIO bits, add hooks appropriately */
+static void alc_fixup_hp_gpio_led(struct hda_codec *codec,
+ int action,
+ unsigned int mute_mask,
+ unsigned int micmute_mask)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_gpio(codec, action, mute_mask | micmute_mask);
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ if (mute_mask) {
+ spec->gpio_mute_led_mask = mute_mask;
+ snd_hda_gen_add_mute_led_cdev(codec, gpio_mute_led_set);
+ }
+ if (micmute_mask) {
+ spec->gpio_mic_led_mask = micmute_mask;
+ snd_hda_gen_add_micmute_led_cdev(codec, micmute_led_set);
+ }
+}
+
+static void alc236_fixup_hp_gpio_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_hp_gpio_led(codec, action, 0x02, 0x01);
+}
+
+static void alc269_fixup_hp_gpio_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10);
+}
+
+static void alc285_fixup_hp_gpio_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_hp_gpio_led(codec, action, 0x04, 0x01);
+}
+
+static void alc286_fixup_hp_gpio_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_hp_gpio_led(codec, action, 0x02, 0x20);
+}
+
+static void alc287_fixup_hp_gpio_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_hp_gpio_led(codec, action, 0x10, 0);
+}
+
+static void alc245_fixup_hp_gpio_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->micmute_led_polarity = 1;
+ alc_fixup_hp_gpio_led(codec, action, 0, 0x04);
+}
+
+/* turn on/off mic-mute LED per capture hook via VREF change */
+static int vref_micmute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_vref_led(codec, spec->cap_mute_led_nid,
+ spec->micmute_led_polarity, brightness);
+ return 0;
+}
+
+static void alc269_fixup_hp_gpio_mic1_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_hp_gpio_led(codec, action, 0x08, 0);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ /* Like hp_gpio_mic1_led, but also needs GPIO4 low to
+ * enable headphone amp
+ */
+ spec->gpio_mask |= 0x10;
+ spec->gpio_dir |= 0x10;
+ spec->cap_mute_led_nid = 0x18;
+ snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set);
+ codec->power_filter = led_power_filter;
+ }
+}
+
+static void alc280_fixup_hp_gpio4(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_hp_gpio_led(codec, action, 0x08, 0);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->cap_mute_led_nid = 0x18;
+ snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set);
+ codec->power_filter = led_power_filter;
+ }
+}
+
+/* HP Spectre x360 14 model needs a unique workaround for enabling the amp;
+ * it needs to toggle the GPIO0 once on and off at each time (bko#210633)
+ */
+static void alc245_fixup_hp_x360_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->gpio_mask |= 0x01;
+ spec->gpio_dir |= 0x01;
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* need to toggle GPIO to enable the amp */
+ alc_update_gpio_data(codec, 0x01, true);
+ msleep(100);
+ alc_update_gpio_data(codec, 0x01, false);
+ break;
+ }
+}
+
+/* toggle GPIO2 at each time stream is started; we use PREPARE state instead */
+static void alc274_hp_envy_pcm_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ switch (action) {
+ case HDA_GEN_PCM_ACT_PREPARE:
+ alc_update_gpio_data(codec, 0x04, true);
+ break;
+ case HDA_GEN_PCM_ACT_CLEANUP:
+ alc_update_gpio_data(codec, 0x04, false);
+ break;
+ }
+}
+
+static void alc274_fixup_hp_envy_gpio(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ spec->gpio_mask |= 0x04;
+ spec->gpio_dir |= 0x04;
+ spec->gen.pcm_playback_hook = alc274_hp_envy_pcm_hook;
+ }
+}
+
+static void alc_update_coef_led(struct hda_codec *codec,
+ struct alc_coef_led *led,
+ bool polarity, bool on)
+{
+ if (polarity)
+ on = !on;
+ /* temporarily power up/down for setting COEF bit */
+ alc_update_coef_idx(codec, led->idx, led->mask,
+ on ? led->on : led->off);
+}
+
+/* update mute-LED according to the speaker mute state via COEF bit */
+static int coef_mute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_coef_led(codec, &spec->mute_led_coef,
+ spec->mute_led_polarity, brightness);
+ return 0;
+}
+
+static void alc285_fixup_hp_mute_led_coefbit(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mute_led_polarity = 0;
+ spec->mute_led_coef.idx = 0x0b;
+ spec->mute_led_coef.mask = 1 << 3;
+ spec->mute_led_coef.on = 1 << 3;
+ spec->mute_led_coef.off = 0;
+ snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set);
+ }
+}
+
+static void alc236_fixup_hp_mute_led_coefbit(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mute_led_polarity = 0;
+ spec->mute_led_coef.idx = 0x34;
+ spec->mute_led_coef.mask = 1 << 5;
+ spec->mute_led_coef.on = 0;
+ spec->mute_led_coef.off = 1 << 5;
+ snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set);
+ }
+}
+
+static void alc236_fixup_hp_mute_led_coefbit2(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mute_led_polarity = 0;
+ spec->mute_led_coef.idx = 0x07;
+ spec->mute_led_coef.mask = 1;
+ spec->mute_led_coef.on = 1;
+ spec->mute_led_coef.off = 0;
+ snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set);
+ }
+}
+
+/* turn on/off mic-mute LED per capture hook by coef bit */
+static int coef_micmute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_coef_led(codec, &spec->mic_led_coef,
+ spec->micmute_led_polarity, brightness);
+ return 0;
+}
+
+static void alc285_fixup_hp_coef_micmute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mic_led_coef.idx = 0x19;
+ spec->mic_led_coef.mask = 1 << 13;
+ spec->mic_led_coef.on = 1 << 13;
+ spec->mic_led_coef.off = 0;
+ snd_hda_gen_add_micmute_led_cdev(codec, coef_micmute_led_set);
+ }
+}
+
+static void alc285_fixup_hp_gpio_micmute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->micmute_led_polarity = 1;
+ alc_fixup_hp_gpio_led(codec, action, 0, 0x04);
+}
+
+static void alc236_fixup_hp_coef_micmute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mic_led_coef.idx = 0x35;
+ spec->mic_led_coef.mask = 3 << 2;
+ spec->mic_led_coef.on = 2 << 2;
+ spec->mic_led_coef.off = 1 << 2;
+ snd_hda_gen_add_micmute_led_cdev(codec, coef_micmute_led_set);
+ }
+}
+
+static void alc285_fixup_hp_mute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc285_fixup_hp_mute_led_coefbit(codec, fix, action);
+ alc285_fixup_hp_coef_micmute_led(codec, fix, action);
+}
+
+static void alc285_fixup_hp_spectre_x360_mute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc285_fixup_hp_mute_led_coefbit(codec, fix, action);
+ alc285_fixup_hp_gpio_micmute_led(codec, fix, action);
+}
+
+static void alc236_fixup_hp_mute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc236_fixup_hp_mute_led_coefbit(codec, fix, action);
+ alc236_fixup_hp_coef_micmute_led(codec, fix, action);
+}
+
+static void alc236_fixup_hp_micmute_led_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->cap_mute_led_nid = 0x1a;
+ snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set);
+ codec->power_filter = led_power_filter;
+ }
+}
+
+static void alc236_fixup_hp_mute_led_micmute_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc236_fixup_hp_mute_led_coefbit(codec, fix, action);
+ alc236_fixup_hp_micmute_led_vref(codec, fix, action);
+}
+
+static inline void alc298_samsung_write_coef_pack(struct hda_codec *codec,
+ const unsigned short coefs[2])
+{
+ alc_write_coef_idx(codec, 0x23, coefs[0]);
+ alc_write_coef_idx(codec, 0x25, coefs[1]);
+ alc_write_coef_idx(codec, 0x26, 0xb011);
+}
+
+struct alc298_samsung_amp_desc {
+ unsigned char nid;
+ unsigned short init_seq[2][2];
+};
+
+static void alc298_fixup_samsung_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ int i, j;
+ static const unsigned short init_seq[][2] = {
+ { 0x19, 0x00 }, { 0x20, 0xc0 }, { 0x22, 0x44 }, { 0x23, 0x08 },
+ { 0x24, 0x85 }, { 0x25, 0x41 }, { 0x35, 0x40 }, { 0x36, 0x01 },
+ { 0x38, 0x81 }, { 0x3a, 0x03 }, { 0x3b, 0x81 }, { 0x40, 0x3e },
+ { 0x41, 0x07 }, { 0x400, 0x1 }
+ };
+ static const struct alc298_samsung_amp_desc amps[] = {
+ { 0x3a, { { 0x18, 0x1 }, { 0x26, 0x0 } } },
+ { 0x39, { { 0x18, 0x2 }, { 0x26, 0x1 } } }
+ };
+
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(amps); i++) {
+ alc_write_coef_idx(codec, 0x22, amps[i].nid);
+
+ for (j = 0; j < ARRAY_SIZE(amps[i].init_seq); j++)
+ alc298_samsung_write_coef_pack(codec, amps[i].init_seq[j]);
+
+ for (j = 0; j < ARRAY_SIZE(init_seq); j++)
+ alc298_samsung_write_coef_pack(codec, init_seq[j]);
+ }
+}
+
+#if IS_REACHABLE(CONFIG_INPUT)
+static void gpio2_mic_hotkey_event(struct hda_codec *codec,
+ struct hda_jack_callback *event)
+{
+ struct alc_spec *spec = codec->spec;
+
+ /* GPIO2 just toggles on a keypress/keyrelease cycle. Therefore
+ send both key on and key off event for every interrupt. */
+ input_report_key(spec->kb_dev, spec->alc_mute_keycode_map[ALC_KEY_MICMUTE_INDEX], 1);
+ input_sync(spec->kb_dev);
+ input_report_key(spec->kb_dev, spec->alc_mute_keycode_map[ALC_KEY_MICMUTE_INDEX], 0);
+ input_sync(spec->kb_dev);
+}
+
+static int alc_register_micmute_input_device(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ int i;
+
+ spec->kb_dev = input_allocate_device();
+ if (!spec->kb_dev) {
+ codec_err(codec, "Out of memory (input_allocate_device)\n");
+ return -ENOMEM;
+ }
+
+ spec->alc_mute_keycode_map[ALC_KEY_MICMUTE_INDEX] = KEY_MICMUTE;
+
+ spec->kb_dev->name = "Microphone Mute Button";
+ spec->kb_dev->evbit[0] = BIT_MASK(EV_KEY);
+ spec->kb_dev->keycodesize = sizeof(spec->alc_mute_keycode_map[0]);
+ spec->kb_dev->keycodemax = ARRAY_SIZE(spec->alc_mute_keycode_map);
+ spec->kb_dev->keycode = spec->alc_mute_keycode_map;
+ for (i = 0; i < ARRAY_SIZE(spec->alc_mute_keycode_map); i++)
+ set_bit(spec->alc_mute_keycode_map[i], spec->kb_dev->keybit);
+
+ if (input_register_device(spec->kb_dev)) {
+ codec_err(codec, "input_register_device failed\n");
+ input_free_device(spec->kb_dev);
+ spec->kb_dev = NULL;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* GPIO1 = set according to SKU external amp
+ * GPIO2 = mic mute hotkey
+ * GPIO3 = mute LED
+ * GPIO4 = mic mute LED
+ */
+static void alc280_fixup_hp_gpio2_mic_hotkey(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->init_amp = ALC_INIT_DEFAULT;
+ if (alc_register_micmute_input_device(codec) != 0)
+ return;
+
+ spec->gpio_mask |= 0x06;
+ spec->gpio_dir |= 0x02;
+ spec->gpio_data |= 0x02;
+ snd_hda_codec_write_cache(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x04);
+ snd_hda_jack_detect_enable_callback(codec, codec->core.afg,
+ gpio2_mic_hotkey_event);
+ return;
+ }
+
+ if (!spec->kb_dev)
+ return;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_FREE:
+ input_unregister_device(spec->kb_dev);
+ spec->kb_dev = NULL;
+ }
+}
+
+/* Line2 = mic mute hotkey
+ * GPIO2 = mic mute LED
+ */
+static void alc233_fixup_lenovo_line2_mic_hotkey(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_hp_gpio_led(codec, action, 0, 0x04);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->init_amp = ALC_INIT_DEFAULT;
+ if (alc_register_micmute_input_device(codec) != 0)
+ return;
+
+ snd_hda_jack_detect_enable_callback(codec, 0x1b,
+ gpio2_mic_hotkey_event);
+ return;
+ }
+
+ if (!spec->kb_dev)
+ return;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_FREE:
+ input_unregister_device(spec->kb_dev);
+ spec->kb_dev = NULL;
+ }
+}
+#else /* INPUT */
+#define alc280_fixup_hp_gpio2_mic_hotkey NULL
+#define alc233_fixup_lenovo_line2_mic_hotkey NULL
+#endif /* INPUT */
+
+static void alc269_fixup_hp_line1_mic1_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x1a);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->cap_mute_led_nid = 0x18;
+ snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set);
+ }
+}
+
+static const struct coef_fw alc225_pre_hsmode[] = {
+ UPDATE_COEF(0x4a, 1<<8, 0),
+ UPDATE_COEFEX(0x57, 0x05, 1<<14, 0),
+ UPDATE_COEF(0x63, 3<<14, 3<<14),
+ UPDATE_COEF(0x4a, 3<<4, 2<<4),
+ UPDATE_COEF(0x4a, 3<<10, 3<<10),
+ UPDATE_COEF(0x45, 0x3f<<10, 0x34<<10),
+ UPDATE_COEF(0x4a, 3<<10, 0),
+ {}
+};
+
+static void alc_headset_mode_unplugged(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ static const struct coef_fw coef0255[] = {
+ WRITE_COEF(0x1b, 0x0c0b), /* LDO and MISC control */
+ WRITE_COEF(0x45, 0xd089), /* UAJ function set to menual mode */
+ UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), /* Direct Drive HP Amp control(Set to verb control)*/
+ WRITE_COEF(0x06, 0x6104), /* Set MIC2 Vref gate with HP */
+ WRITE_COEFEX(0x57, 0x03, 0x8aa6), /* Direct Drive HP Amp control */
+ {}
+ };
+ static const struct coef_fw coef0256[] = {
+ WRITE_COEF(0x1b, 0x0c4b), /* LDO and MISC control */
+ WRITE_COEF(0x45, 0xd089), /* UAJ function set to menual mode */
+ WRITE_COEF(0x06, 0x6104), /* Set MIC2 Vref gate with HP */
+ WRITE_COEFEX(0x57, 0x03, 0x09a3), /* Direct Drive HP Amp control */
+ UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), /* Direct Drive HP Amp control(Set to verb control)*/
+ {}
+ };
+ static const struct coef_fw coef0233[] = {
+ WRITE_COEF(0x1b, 0x0c0b),
+ WRITE_COEF(0x45, 0xc429),
+ UPDATE_COEF(0x35, 0x4000, 0),
+ WRITE_COEF(0x06, 0x2104),
+ WRITE_COEF(0x1a, 0x0001),
+ WRITE_COEF(0x26, 0x0004),
+ WRITE_COEF(0x32, 0x42a3),
+ {}
+ };
+ static const struct coef_fw coef0288[] = {
+ UPDATE_COEF(0x4f, 0xfcc0, 0xc400),
+ UPDATE_COEF(0x50, 0x2000, 0x2000),
+ UPDATE_COEF(0x56, 0x0006, 0x0006),
+ UPDATE_COEF(0x66, 0x0008, 0),
+ UPDATE_COEF(0x67, 0x2000, 0),
+ {}
+ };
+ static const struct coef_fw coef0298[] = {
+ UPDATE_COEF(0x19, 0x1300, 0x0300),
+ {}
+ };
+ static const struct coef_fw coef0292[] = {
+ WRITE_COEF(0x76, 0x000e),
+ WRITE_COEF(0x6c, 0x2400),
+ WRITE_COEF(0x18, 0x7308),
+ WRITE_COEF(0x6b, 0xc429),
+ {}
+ };
+ static const struct coef_fw coef0293[] = {
+ UPDATE_COEF(0x10, 7<<8, 6<<8), /* SET Line1 JD to 0 */
+ UPDATE_COEFEX(0x57, 0x05, 1<<15|1<<13, 0x0), /* SET charge pump by verb */
+ UPDATE_COEFEX(0x57, 0x03, 1<<10, 1<<10), /* SET EN_OSW to 1 */
+ UPDATE_COEF(0x1a, 1<<3, 1<<3), /* Combo JD gating with LINE1-VREFO */
+ WRITE_COEF(0x45, 0xc429), /* Set to TRS type */
+ UPDATE_COEF(0x4a, 0x000f, 0x000e), /* Combo Jack auto detect */
+ {}
+ };
+ static const struct coef_fw coef0668[] = {
+ WRITE_COEF(0x15, 0x0d40),
+ WRITE_COEF(0xb7, 0x802b),
+ {}
+ };
+ static const struct coef_fw coef0225[] = {
+ UPDATE_COEF(0x63, 3<<14, 0),
+ {}
+ };
+ static const struct coef_fw coef0274[] = {
+ UPDATE_COEF(0x4a, 0x0100, 0),
+ UPDATE_COEFEX(0x57, 0x05, 0x4000, 0),
+ UPDATE_COEF(0x6b, 0xf000, 0x5000),
+ UPDATE_COEF(0x4a, 0x0010, 0),
+ UPDATE_COEF(0x4a, 0x0c00, 0x0c00),
+ WRITE_COEF(0x45, 0x5289),
+ UPDATE_COEF(0x4a, 0x0c00, 0),
+ {}
+ };
+
+ if (spec->no_internal_mic_pin) {
+ alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12);
+ return;
+ }
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0255:
+ alc_process_coef_fw(codec, coef0255);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_process_coef_fw(codec, coef0256);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ alc_process_coef_fw(codec, coef0274);
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ alc_process_coef_fw(codec, coef0233);
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ alc_process_coef_fw(codec, coef0288);
+ break;
+ case 0x10ec0298:
+ alc_process_coef_fw(codec, coef0298);
+ alc_process_coef_fw(codec, coef0288);
+ break;
+ case 0x10ec0292:
+ alc_process_coef_fw(codec, coef0292);
+ break;
+ case 0x10ec0293:
+ alc_process_coef_fw(codec, coef0293);
+ break;
+ case 0x10ec0668:
+ alc_process_coef_fw(codec, coef0668);
+ break;
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ alc_process_coef_fw(codec, alc225_pre_hsmode);
+ alc_process_coef_fw(codec, coef0225);
+ break;
+ case 0x10ec0867:
+ alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0);
+ break;
+ }
+ codec_dbg(codec, "Headset jack set to unplugged mode.\n");
+}
+
+
+static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin,
+ hda_nid_t mic_pin)
+{
+ static const struct coef_fw coef0255[] = {
+ WRITE_COEFEX(0x57, 0x03, 0x8aa6),
+ WRITE_COEF(0x06, 0x6100), /* Set MIC2 Vref gate to normal */
+ {}
+ };
+ static const struct coef_fw coef0256[] = {
+ UPDATE_COEFEX(0x57, 0x05, 1<<14, 1<<14), /* Direct Drive HP Amp control(Set to verb control)*/
+ WRITE_COEFEX(0x57, 0x03, 0x09a3),
+ WRITE_COEF(0x06, 0x6100), /* Set MIC2 Vref gate to normal */
+ {}
+ };
+ static const struct coef_fw coef0233[] = {
+ UPDATE_COEF(0x35, 0, 1<<14),
+ WRITE_COEF(0x06, 0x2100),
+ WRITE_COEF(0x1a, 0x0021),
+ WRITE_COEF(0x26, 0x008c),
+ {}
+ };
+ static const struct coef_fw coef0288[] = {
+ UPDATE_COEF(0x4f, 0x00c0, 0),
+ UPDATE_COEF(0x50, 0x2000, 0),
+ UPDATE_COEF(0x56, 0x0006, 0),
+ UPDATE_COEF(0x4f, 0xfcc0, 0xc400),
+ UPDATE_COEF(0x66, 0x0008, 0x0008),
+ UPDATE_COEF(0x67, 0x2000, 0x2000),
+ {}
+ };
+ static const struct coef_fw coef0292[] = {
+ WRITE_COEF(0x19, 0xa208),
+ WRITE_COEF(0x2e, 0xacf0),
+ {}
+ };
+ static const struct coef_fw coef0293[] = {
+ UPDATE_COEFEX(0x57, 0x05, 0, 1<<15|1<<13), /* SET charge pump by verb */
+ UPDATE_COEFEX(0x57, 0x03, 1<<10, 0), /* SET EN_OSW to 0 */
+ UPDATE_COEF(0x1a, 1<<3, 0), /* Combo JD gating without LINE1-VREFO */
+ {}
+ };
+ static const struct coef_fw coef0688[] = {
+ WRITE_COEF(0xb7, 0x802b),
+ WRITE_COEF(0xb5, 0x1040),
+ UPDATE_COEF(0xc3, 0, 1<<12),
+ {}
+ };
+ static const struct coef_fw coef0225[] = {
+ UPDATE_COEFEX(0x57, 0x05, 1<<14, 1<<14),
+ UPDATE_COEF(0x4a, 3<<4, 2<<4),
+ UPDATE_COEF(0x63, 3<<14, 0),
+ {}
+ };
+ static const struct coef_fw coef0274[] = {
+ UPDATE_COEFEX(0x57, 0x05, 0x4000, 0x4000),
+ UPDATE_COEF(0x4a, 0x0010, 0),
+ UPDATE_COEF(0x6b, 0xf000, 0),
+ {}
+ };
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0255:
+ alc_write_coef_idx(codec, 0x45, 0xc489);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0255);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_write_coef_idx(codec, 0x45, 0xc489);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0256);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ alc_write_coef_idx(codec, 0x45, 0x4689);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0274);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ alc_write_coef_idx(codec, 0x45, 0xc429);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0233);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ case 0x10ec0298:
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0288);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0292:
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0292);
+ break;
+ case 0x10ec0293:
+ /* Set to TRS mode */
+ alc_write_coef_idx(codec, 0x45, 0xc429);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0293);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0867:
+ alc_update_coefex_idx(codec, 0x57, 0x5, 0, 1<<14);
+ fallthrough;
+ case 0x10ec0221:
+ case 0x10ec0662:
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0668:
+ alc_write_coef_idx(codec, 0x11, 0x0001);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0688);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ alc_process_coef_fw(codec, alc225_pre_hsmode);
+ alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x31<<10);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ alc_process_coef_fw(codec, coef0225);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
+ }
+ codec_dbg(codec, "Headset jack set to mic-in mode.\n");
+}
+
+static void alc_headset_mode_default(struct hda_codec *codec)
+{
+ static const struct coef_fw coef0225[] = {
+ UPDATE_COEF(0x45, 0x3f<<10, 0x30<<10),
+ UPDATE_COEF(0x45, 0x3f<<10, 0x31<<10),
+ UPDATE_COEF(0x49, 3<<8, 0<<8),
+ UPDATE_COEF(0x4a, 3<<4, 3<<4),
+ UPDATE_COEF(0x63, 3<<14, 0),
+ UPDATE_COEF(0x67, 0xf000, 0x3000),
+ {}
+ };
+ static const struct coef_fw coef0255[] = {
+ WRITE_COEF(0x45, 0xc089),
+ WRITE_COEF(0x45, 0xc489),
+ WRITE_COEFEX(0x57, 0x03, 0x8ea6),
+ WRITE_COEF(0x49, 0x0049),
+ {}
+ };
+ static const struct coef_fw coef0256[] = {
+ WRITE_COEF(0x45, 0xc489),
+ WRITE_COEFEX(0x57, 0x03, 0x0da3),
+ WRITE_COEF(0x49, 0x0049),
+ UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), /* Direct Drive HP Amp control(Set to verb control)*/
+ WRITE_COEF(0x06, 0x6100),
+ {}
+ };
+ static const struct coef_fw coef0233[] = {
+ WRITE_COEF(0x06, 0x2100),
+ WRITE_COEF(0x32, 0x4ea3),
+ {}
+ };
+ static const struct coef_fw coef0288[] = {
+ UPDATE_COEF(0x4f, 0xfcc0, 0xc400), /* Set to TRS type */
+ UPDATE_COEF(0x50, 0x2000, 0x2000),
+ UPDATE_COEF(0x56, 0x0006, 0x0006),
+ UPDATE_COEF(0x66, 0x0008, 0),
+ UPDATE_COEF(0x67, 0x2000, 0),
+ {}
+ };
+ static const struct coef_fw coef0292[] = {
+ WRITE_COEF(0x76, 0x000e),
+ WRITE_COEF(0x6c, 0x2400),
+ WRITE_COEF(0x6b, 0xc429),
+ WRITE_COEF(0x18, 0x7308),
+ {}
+ };
+ static const struct coef_fw coef0293[] = {
+ UPDATE_COEF(0x4a, 0x000f, 0x000e), /* Combo Jack auto detect */
+ WRITE_COEF(0x45, 0xC429), /* Set to TRS type */
+ UPDATE_COEF(0x1a, 1<<3, 0), /* Combo JD gating without LINE1-VREFO */
+ {}
+ };
+ static const struct coef_fw coef0688[] = {
+ WRITE_COEF(0x11, 0x0041),
+ WRITE_COEF(0x15, 0x0d40),
+ WRITE_COEF(0xb7, 0x802b),
+ {}
+ };
+ static const struct coef_fw coef0274[] = {
+ WRITE_COEF(0x45, 0x4289),
+ UPDATE_COEF(0x4a, 0x0010, 0x0010),
+ UPDATE_COEF(0x6b, 0x0f00, 0),
+ UPDATE_COEF(0x49, 0x0300, 0x0300),
+ {}
+ };
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ alc_process_coef_fw(codec, alc225_pre_hsmode);
+ alc_process_coef_fw(codec, coef0225);
+ break;
+ case 0x10ec0255:
+ alc_process_coef_fw(codec, coef0255);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_write_coef_idx(codec, 0x1b, 0x0e4b);
+ alc_write_coef_idx(codec, 0x45, 0xc089);
+ msleep(50);
+ alc_process_coef_fw(codec, coef0256);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ alc_process_coef_fw(codec, coef0274);
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ alc_process_coef_fw(codec, coef0233);
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ case 0x10ec0298:
+ alc_process_coef_fw(codec, coef0288);
+ break;
+ case 0x10ec0292:
+ alc_process_coef_fw(codec, coef0292);
+ break;
+ case 0x10ec0293:
+ alc_process_coef_fw(codec, coef0293);
+ break;
+ case 0x10ec0668:
+ alc_process_coef_fw(codec, coef0688);
+ break;
+ case 0x10ec0867:
+ alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0);
+ break;
+ }
+ codec_dbg(codec, "Headset jack set to headphone (default) mode.\n");
+}
+
+/* Iphone type */
+static void alc_headset_mode_ctia(struct hda_codec *codec)
+{
+ int val;
+
+ static const struct coef_fw coef0255[] = {
+ WRITE_COEF(0x45, 0xd489), /* Set to CTIA type */
+ WRITE_COEF(0x1b, 0x0c2b),
+ WRITE_COEFEX(0x57, 0x03, 0x8ea6),
+ {}
+ };
+ static const struct coef_fw coef0256[] = {
+ WRITE_COEF(0x45, 0xd489), /* Set to CTIA type */
+ WRITE_COEF(0x1b, 0x0e6b),
+ {}
+ };
+ static const struct coef_fw coef0233[] = {
+ WRITE_COEF(0x45, 0xd429),
+ WRITE_COEF(0x1b, 0x0c2b),
+ WRITE_COEF(0x32, 0x4ea3),
+ {}
+ };
+ static const struct coef_fw coef0288[] = {
+ UPDATE_COEF(0x50, 0x2000, 0x2000),
+ UPDATE_COEF(0x56, 0x0006, 0x0006),
+ UPDATE_COEF(0x66, 0x0008, 0),
+ UPDATE_COEF(0x67, 0x2000, 0),
+ {}
+ };
+ static const struct coef_fw coef0292[] = {
+ WRITE_COEF(0x6b, 0xd429),
+ WRITE_COEF(0x76, 0x0008),
+ WRITE_COEF(0x18, 0x7388),
+ {}
+ };
+ static const struct coef_fw coef0293[] = {
+ WRITE_COEF(0x45, 0xd429), /* Set to ctia type */
+ UPDATE_COEF(0x10, 7<<8, 7<<8), /* SET Line1 JD to 1 */
+ {}
+ };
+ static const struct coef_fw coef0688[] = {
+ WRITE_COEF(0x11, 0x0001),
+ WRITE_COEF(0x15, 0x0d60),
+ WRITE_COEF(0xc3, 0x0000),
+ {}
+ };
+ static const struct coef_fw coef0225_1[] = {
+ UPDATE_COEF(0x45, 0x3f<<10, 0x35<<10),
+ UPDATE_COEF(0x63, 3<<14, 2<<14),
+ {}
+ };
+ static const struct coef_fw coef0225_2[] = {
+ UPDATE_COEF(0x45, 0x3f<<10, 0x35<<10),
+ UPDATE_COEF(0x63, 3<<14, 1<<14),
+ {}
+ };
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0255:
+ alc_process_coef_fw(codec, coef0255);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_process_coef_fw(codec, coef0256);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ alc_write_coef_idx(codec, 0x45, 0xd689);
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ alc_process_coef_fw(codec, coef0233);
+ break;
+ case 0x10ec0298:
+ val = alc_read_coef_idx(codec, 0x50);
+ if (val & (1 << 12)) {
+ alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0020);
+ alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xd400);
+ msleep(300);
+ } else {
+ alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0010);
+ alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xd400);
+ msleep(300);
+ }
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xd400);
+ msleep(300);
+ alc_process_coef_fw(codec, coef0288);
+ break;
+ case 0x10ec0292:
+ alc_process_coef_fw(codec, coef0292);
+ break;
+ case 0x10ec0293:
+ alc_process_coef_fw(codec, coef0293);
+ break;
+ case 0x10ec0668:
+ alc_process_coef_fw(codec, coef0688);
+ break;
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ val = alc_read_coef_idx(codec, 0x45);
+ if (val & (1 << 9))
+ alc_process_coef_fw(codec, coef0225_2);
+ else
+ alc_process_coef_fw(codec, coef0225_1);
+ break;
+ case 0x10ec0867:
+ alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0);
+ break;
+ }
+ codec_dbg(codec, "Headset jack set to iPhone-style headset mode.\n");
+}
+
+/* Nokia type */
+static void alc_headset_mode_omtp(struct hda_codec *codec)
+{
+ static const struct coef_fw coef0255[] = {
+ WRITE_COEF(0x45, 0xe489), /* Set to OMTP Type */
+ WRITE_COEF(0x1b, 0x0c2b),
+ WRITE_COEFEX(0x57, 0x03, 0x8ea6),
+ {}
+ };
+ static const struct coef_fw coef0256[] = {
+ WRITE_COEF(0x45, 0xe489), /* Set to OMTP Type */
+ WRITE_COEF(0x1b, 0x0e6b),
+ {}
+ };
+ static const struct coef_fw coef0233[] = {
+ WRITE_COEF(0x45, 0xe429),
+ WRITE_COEF(0x1b, 0x0c2b),
+ WRITE_COEF(0x32, 0x4ea3),
+ {}
+ };
+ static const struct coef_fw coef0288[] = {
+ UPDATE_COEF(0x50, 0x2000, 0x2000),
+ UPDATE_COEF(0x56, 0x0006, 0x0006),
+ UPDATE_COEF(0x66, 0x0008, 0),
+ UPDATE_COEF(0x67, 0x2000, 0),
+ {}
+ };
+ static const struct coef_fw coef0292[] = {
+ WRITE_COEF(0x6b, 0xe429),
+ WRITE_COEF(0x76, 0x0008),
+ WRITE_COEF(0x18, 0x7388),
+ {}
+ };
+ static const struct coef_fw coef0293[] = {
+ WRITE_COEF(0x45, 0xe429), /* Set to omtp type */
+ UPDATE_COEF(0x10, 7<<8, 7<<8), /* SET Line1 JD to 1 */
+ {}
+ };
+ static const struct coef_fw coef0688[] = {
+ WRITE_COEF(0x11, 0x0001),
+ WRITE_COEF(0x15, 0x0d50),
+ WRITE_COEF(0xc3, 0x0000),
+ {}
+ };
+ static const struct coef_fw coef0225[] = {
+ UPDATE_COEF(0x45, 0x3f<<10, 0x39<<10),
+ UPDATE_COEF(0x63, 3<<14, 2<<14),
+ {}
+ };
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0255:
+ alc_process_coef_fw(codec, coef0255);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_process_coef_fw(codec, coef0256);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ alc_write_coef_idx(codec, 0x45, 0xe689);
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ alc_process_coef_fw(codec, coef0233);
+ break;
+ case 0x10ec0298:
+ alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0010);/* Headset output enable */
+ alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xe400);
+ msleep(300);
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xe400);
+ msleep(300);
+ alc_process_coef_fw(codec, coef0288);
+ break;
+ case 0x10ec0292:
+ alc_process_coef_fw(codec, coef0292);
+ break;
+ case 0x10ec0293:
+ alc_process_coef_fw(codec, coef0293);
+ break;
+ case 0x10ec0668:
+ alc_process_coef_fw(codec, coef0688);
+ break;
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ alc_process_coef_fw(codec, coef0225);
+ break;
+ }
+ codec_dbg(codec, "Headset jack set to Nokia-style headset mode.\n");
+}
+
+static void alc_determine_headset_type(struct hda_codec *codec)
+{
+ int val;
+ bool is_ctia = false;
+ struct alc_spec *spec = codec->spec;
+ static const struct coef_fw coef0255[] = {
+ WRITE_COEF(0x45, 0xd089), /* combo jack auto switch control(Check type)*/
+ WRITE_COEF(0x49, 0x0149), /* combo jack auto switch control(Vref
+ conteol) */
+ {}
+ };
+ static const struct coef_fw coef0288[] = {
+ UPDATE_COEF(0x4f, 0xfcc0, 0xd400), /* Check Type */
+ {}
+ };
+ static const struct coef_fw coef0298[] = {
+ UPDATE_COEF(0x50, 0x2000, 0x2000),
+ UPDATE_COEF(0x56, 0x0006, 0x0006),
+ UPDATE_COEF(0x66, 0x0008, 0),
+ UPDATE_COEF(0x67, 0x2000, 0),
+ UPDATE_COEF(0x19, 0x1300, 0x1300),
+ {}
+ };
+ static const struct coef_fw coef0293[] = {
+ UPDATE_COEF(0x4a, 0x000f, 0x0008), /* Combo Jack auto detect */
+ WRITE_COEF(0x45, 0xD429), /* Set to ctia type */
+ {}
+ };
+ static const struct coef_fw coef0688[] = {
+ WRITE_COEF(0x11, 0x0001),
+ WRITE_COEF(0xb7, 0x802b),
+ WRITE_COEF(0x15, 0x0d60),
+ WRITE_COEF(0xc3, 0x0c00),
+ {}
+ };
+ static const struct coef_fw coef0274[] = {
+ UPDATE_COEF(0x4a, 0x0010, 0),
+ UPDATE_COEF(0x4a, 0x8000, 0),
+ WRITE_COEF(0x45, 0xd289),
+ UPDATE_COEF(0x49, 0x0300, 0x0300),
+ {}
+ };
+
+ if (spec->no_internal_mic_pin) {
+ alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12);
+ return;
+ }
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0255:
+ alc_process_coef_fw(codec, coef0255);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x0070) == 0x0070;
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_write_coef_idx(codec, 0x1b, 0x0e4b);
+ alc_write_coef_idx(codec, 0x06, 0x6104);
+ alc_write_coefex_idx(codec, 0x57, 0x3, 0x09a3);
+
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ msleep(80);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ alc_process_coef_fw(codec, coef0255);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x0070) == 0x0070;
+
+ alc_write_coefex_idx(codec, 0x57, 0x3, 0x0da3);
+ alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0);
+
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+ msleep(80);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ alc_process_coef_fw(codec, coef0274);
+ msleep(850);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x00f0) == 0x00f0;
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ alc_write_coef_idx(codec, 0x45, 0xd029);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x0070) == 0x0070;
+ break;
+ case 0x10ec0298:
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ msleep(100);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+ msleep(200);
+
+ val = alc_read_coef_idx(codec, 0x50);
+ if (val & (1 << 12)) {
+ alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0020);
+ alc_process_coef_fw(codec, coef0288);
+ msleep(350);
+ val = alc_read_coef_idx(codec, 0x50);
+ is_ctia = (val & 0x0070) == 0x0070;
+ } else {
+ alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0010);
+ alc_process_coef_fw(codec, coef0288);
+ msleep(350);
+ val = alc_read_coef_idx(codec, 0x50);
+ is_ctia = (val & 0x0070) == 0x0070;
+ }
+ alc_process_coef_fw(codec, coef0298);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
+ msleep(75);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ alc_process_coef_fw(codec, coef0288);
+ msleep(350);
+ val = alc_read_coef_idx(codec, 0x50);
+ is_ctia = (val & 0x0070) == 0x0070;
+ break;
+ case 0x10ec0292:
+ alc_write_coef_idx(codec, 0x6b, 0xd429);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0x6c);
+ is_ctia = (val & 0x001c) == 0x001c;
+ break;
+ case 0x10ec0293:
+ alc_process_coef_fw(codec, coef0293);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x0070) == 0x0070;
+ break;
+ case 0x10ec0668:
+ alc_process_coef_fw(codec, coef0688);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0xbe);
+ is_ctia = (val & 0x1c02) == 0x1c02;
+ break;
+ case 0x10ec0215:
+ case 0x10ec0225:
+ case 0x10ec0285:
+ case 0x10ec0295:
+ case 0x10ec0289:
+ case 0x10ec0299:
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ msleep(80);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0);
+
+ alc_process_coef_fw(codec, alc225_pre_hsmode);
+ alc_update_coef_idx(codec, 0x67, 0xf000, 0x1000);
+ val = alc_read_coef_idx(codec, 0x45);
+ if (val & (1 << 9)) {
+ alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x34<<10);
+ alc_update_coef_idx(codec, 0x49, 3<<8, 2<<8);
+ msleep(800);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x00f0) == 0x00f0;
+ } else {
+ alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x34<<10);
+ alc_update_coef_idx(codec, 0x49, 3<<8, 1<<8);
+ msleep(800);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x00f0) == 0x00f0;
+ }
+ alc_update_coef_idx(codec, 0x4a, 7<<6, 7<<6);
+ alc_update_coef_idx(codec, 0x4a, 3<<4, 3<<4);
+ alc_update_coef_idx(codec, 0x67, 0xf000, 0x3000);
+
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+ msleep(80);
+ snd_hda_codec_write(codec, 0x21, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
+ break;
+ case 0x10ec0867:
+ is_ctia = true;
+ break;
+ }
+
+ codec_dbg(codec, "Headset jack detected iPhone-style headset: %s\n",
+ is_ctia ? "yes" : "no");
+ spec->current_headset_type = is_ctia ? ALC_HEADSET_TYPE_CTIA : ALC_HEADSET_TYPE_OMTP;
+}
+
+static void alc_update_headset_mode(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+
+ hda_nid_t mux_pin = spec->gen.imux_pins[spec->gen.cur_mux[0]];
+ hda_nid_t hp_pin = alc_get_hp_pin(spec);
+
+ int new_headset_mode;
+
+ if (!snd_hda_jack_detect(codec, hp_pin))
+ new_headset_mode = ALC_HEADSET_MODE_UNPLUGGED;
+ else if (mux_pin == spec->headset_mic_pin)
+ new_headset_mode = ALC_HEADSET_MODE_HEADSET;
+ else if (mux_pin == spec->headphone_mic_pin)
+ new_headset_mode = ALC_HEADSET_MODE_MIC;
+ else
+ new_headset_mode = ALC_HEADSET_MODE_HEADPHONE;
+
+ if (new_headset_mode == spec->current_headset_mode) {
+ snd_hda_gen_update_outputs(codec);
+ return;
+ }
+
+ switch (new_headset_mode) {
+ case ALC_HEADSET_MODE_UNPLUGGED:
+ alc_headset_mode_unplugged(codec);
+ spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN;
+ spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN;
+ spec->gen.hp_jack_present = false;
+ break;
+ case ALC_HEADSET_MODE_HEADSET:
+ if (spec->current_headset_type == ALC_HEADSET_TYPE_UNKNOWN)
+ alc_determine_headset_type(codec);
+ if (spec->current_headset_type == ALC_HEADSET_TYPE_CTIA)
+ alc_headset_mode_ctia(codec);
+ else if (spec->current_headset_type == ALC_HEADSET_TYPE_OMTP)
+ alc_headset_mode_omtp(codec);
+ spec->gen.hp_jack_present = true;
+ break;
+ case ALC_HEADSET_MODE_MIC:
+ alc_headset_mode_mic_in(codec, hp_pin, spec->headphone_mic_pin);
+ spec->gen.hp_jack_present = false;
+ break;
+ case ALC_HEADSET_MODE_HEADPHONE:
+ alc_headset_mode_default(codec);
+ spec->gen.hp_jack_present = true;
+ break;
+ }
+ if (new_headset_mode != ALC_HEADSET_MODE_MIC) {
+ snd_hda_set_pin_ctl_cache(codec, hp_pin,
+ AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
+ if (spec->headphone_mic_pin && spec->headphone_mic_pin != hp_pin)
+ snd_hda_set_pin_ctl_cache(codec, spec->headphone_mic_pin,
+ PIN_VREFHIZ);
+ }
+ spec->current_headset_mode = new_headset_mode;
+
+ snd_hda_gen_update_outputs(codec);
+}
+
+static void alc_update_headset_mode_hook(struct hda_codec *codec,
+ struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ alc_update_headset_mode(codec);
+}
+
+static void alc_update_headset_jack_cb(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ snd_hda_gen_hp_automute(codec, jack);
+ alc_update_headset_mode(codec);
+}
+
+static void alc_probe_headset_mode(struct hda_codec *codec)
+{
+ int i;
+ struct alc_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+
+ /* Find mic pins */
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].is_headset_mic && !spec->headset_mic_pin)
+ spec->headset_mic_pin = cfg->inputs[i].pin;
+ if (cfg->inputs[i].is_headphone_mic && !spec->headphone_mic_pin)
+ spec->headphone_mic_pin = cfg->inputs[i].pin;
+ }
+
+ WARN_ON(spec->gen.cap_sync_hook);
+ spec->gen.cap_sync_hook = alc_update_headset_mode_hook;
+ spec->gen.automute_hook = alc_update_headset_mode;
+ spec->gen.hp_automute_hook = alc_update_headset_jack_cb;
+}
+
+static void alc_fixup_headset_mode(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC | HDA_PINCFG_HEADPHONE_MIC;
+ break;
+ case HDA_FIXUP_ACT_PROBE:
+ alc_probe_headset_mode(codec);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ if (is_s3_resume(codec) || is_s4_resume(codec)) {
+ spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN;
+ spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN;
+ }
+ alc_update_headset_mode(codec);
+ break;
+ }
+}
+
+static void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ }
+ else
+ alc_fixup_headset_mode(codec, fix, action);
+}
+
+static void alc255_set_default_jack_type(struct hda_codec *codec)
+{
+ /* Set to iphone type */
+ static const struct coef_fw alc255fw[] = {
+ WRITE_COEF(0x1b, 0x880b),
+ WRITE_COEF(0x45, 0xd089),
+ WRITE_COEF(0x1b, 0x080b),
+ WRITE_COEF(0x46, 0x0004),
+ WRITE_COEF(0x1b, 0x0c0b),
+ {}
+ };
+ static const struct coef_fw alc256fw[] = {
+ WRITE_COEF(0x1b, 0x884b),
+ WRITE_COEF(0x45, 0xd089),
+ WRITE_COEF(0x1b, 0x084b),
+ WRITE_COEF(0x46, 0x0004),
+ WRITE_COEF(0x1b, 0x0c4b),
+ {}
+ };
+ switch (codec->core.vendor_id) {
+ case 0x10ec0255:
+ alc_process_coef_fw(codec, alc255fw);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ alc_process_coef_fw(codec, alc256fw);
+ break;
+ }
+ msleep(30);
+}
+
+static void alc_fixup_headset_mode_alc255(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ alc255_set_default_jack_type(codec);
+ }
+ alc_fixup_headset_mode(codec, fix, action);
+}
+
+static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ alc255_set_default_jack_type(codec);
+ }
+ else
+ alc_fixup_headset_mode(codec, fix, action);
+}
+
+static void alc288_update_headset_jack_cb(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_update_headset_jack_cb(codec, jack);
+ /* Headset Mic enable or disable, only for Dell Dino */
+ alc_update_gpio_data(codec, 0x40, spec->gen.hp_jack_present);
+}
+
+static void alc_fixup_headset_mode_dell_alc288(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_headset_mode(codec, fix, action);
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ /* toggled via hp_automute_hook */
+ spec->gpio_mask |= 0x40;
+ spec->gpio_dir |= 0x40;
+ spec->gen.hp_automute_hook = alc288_update_headset_jack_cb;
+ }
+}
+
+static void alc_fixup_auto_mute_via_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ spec->gen.auto_mute_via_amp = 1;
+ }
+}
+
+static void alc_fixup_no_shutup(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ spec->no_shutup_pins = 1;
+ }
+}
+
+static void alc_fixup_disable_aamix(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ /* Disable AA-loopback as it causes white noise */
+ spec->gen.mixer_nid = 0;
+ }
+}
+
+/* fixup for Thinkpad docks: add dock pins, avoid HP parser fixup */
+static void alc_fixup_tpt440_dock(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x16, 0x21211010 }, /* dock headphone */
+ { 0x19, 0x21a11010 }, /* dock mic */
+ { }
+ };
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->reboot_notify = snd_hda_gen_reboot_notify; /* reduce noise */
+ spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP;
+ codec->power_save_node = 0; /* avoid click noises */
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ }
+}
+
+static void alc_fixup_tpt470_dock(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x17, 0x21211010 }, /* dock headphone */
+ { 0x19, 0x21a11010 }, /* dock mic */
+ { }
+ };
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP;
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ } else if (action == HDA_FIXUP_ACT_INIT) {
+ /* Enable DOCK device */
+ snd_hda_codec_write(codec, 0x17, 0,
+ AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, 0);
+ /* Enable DOCK device */
+ snd_hda_codec_write(codec, 0x19, 0,
+ AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, 0);
+ }
+}
+
+static void alc_fixup_tpt470_dacs(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ /* Assure the speaker pin to be coupled with DAC NID 0x03; otherwise
+ * the speaker output becomes too low by some reason on Thinkpads with
+ * ALC298 codec
+ */
+ static const hda_nid_t preferred_pairs[] = {
+ 0x14, 0x03, 0x17, 0x02, 0x21, 0x02,
+ 0
+ };
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->gen.preferred_dacs = preferred_pairs;
+}
+
+static void alc295_fixup_asus_dacs(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t preferred_pairs[] = {
+ 0x17, 0x02, 0x21, 0x03, 0
+ };
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->gen.preferred_dacs = preferred_pairs;
+}
+
+static void alc_shutup_dell_xps13(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ int hp_pin = alc_get_hp_pin(spec);
+
+ /* Prevent pop noises when headphones are plugged in */
+ snd_hda_codec_write(codec, hp_pin, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ msleep(20);
+}
+
+static void alc_fixup_dell_xps13(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ struct hda_input_mux *imux = &spec->gen.input_mux;
+ int i;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ /* mic pin 0x19 must be initialized with Vref Hi-Z, otherwise
+ * it causes a click noise at start up
+ */
+ snd_hda_codec_set_pin_target(codec, 0x19, PIN_VREFHIZ);
+ spec->shutup = alc_shutup_dell_xps13;
+ break;
+ case HDA_FIXUP_ACT_PROBE:
+ /* Make the internal mic the default input source. */
+ for (i = 0; i < imux->num_items; i++) {
+ if (spec->gen.imux_pins[i] == 0x12) {
+ spec->gen.cur_mux[0] = i;
+ break;
+ }
+ }
+ break;
+ }
+}
+
+static void alc_fixup_headset_mode_alc662(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ spec->gen.hp_mic = 1; /* Mic-in is same pin as headphone */
+
+ /* Disable boost for mic-in permanently. (This code is only called
+ from quirks that guarantee that the headphone is at NID 0x1b.) */
+ snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000);
+ snd_hda_override_wcaps(codec, 0x1b, get_wcaps(codec, 0x1b) & ~AC_WCAP_IN_AMP);
+ } else
+ alc_fixup_headset_mode(codec, fix, action);
+}
+
+static void alc_fixup_headset_mode_alc668(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ alc_write_coef_idx(codec, 0xc4, 0x8000);
+ alc_update_coef_idx(codec, 0xc2, ~0xfe, 0);
+ snd_hda_set_pin_ctl_cache(codec, 0x18, 0);
+ }
+ alc_fixup_headset_mode(codec, fix, action);
+}
+
+/* Returns the nid of the external mic input pin, or 0 if it cannot be found. */
+static int find_ext_mic_pin(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ hda_nid_t nid;
+ unsigned int defcfg;
+ int i;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].type != AUTO_PIN_MIC)
+ continue;
+ nid = cfg->inputs[i].pin;
+ defcfg = snd_hda_codec_get_pincfg(codec, nid);
+ if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT)
+ continue;
+ return nid;
+ }
+
+ return 0;
+}
+
+static void alc271_hp_gate_mic_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ int mic_pin = find_ext_mic_pin(codec);
+ int hp_pin = alc_get_hp_pin(spec);
+
+ if (snd_BUG_ON(!mic_pin || !hp_pin))
+ return;
+ snd_hda_jack_set_gating_jack(codec, mic_pin, hp_pin);
+ }
+}
+
+static void alc269_fixup_limit_int_mic_boost(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ int i;
+
+ /* The mic boosts on level 2 and 3 are too noisy
+ on the internal mic input.
+ Therefore limit the boost to 0 or 1. */
+
+ if (action != HDA_FIXUP_ACT_PROBE)
+ return;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t nid = cfg->inputs[i].pin;
+ unsigned int defcfg;
+ if (cfg->inputs[i].type != AUTO_PIN_MIC)
+ continue;
+ defcfg = snd_hda_codec_get_pincfg(codec, nid);
+ if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT)
+ continue;
+
+ snd_hda_override_amp_caps(codec, nid, HDA_INPUT,
+ (0x00 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+ }
+}
+
+static void alc283_hp_automute_hook(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+ int vref;
+
+ msleep(200);
+ snd_hda_gen_hp_automute(codec, jack);
+
+ vref = spec->gen.hp_jack_present ? PIN_VREF80 : 0;
+
+ msleep(600);
+ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+ vref);
+}
+
+static void alc283_fixup_chromebook(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_override_wcaps(codec, 0x03, 0);
+ /* Disable AA-loopback as it causes white noise */
+ spec->gen.mixer_nid = 0;
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* MIC2-VREF control */
+ /* Set to manual mode */
+ alc_update_coef_idx(codec, 0x06, 0x000c, 0);
+ /* Enable Line1 input control by verb */
+ alc_update_coef_idx(codec, 0x1a, 0, 1 << 4);
+ break;
+ }
+}
+
+static void alc283_fixup_sense_combo_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->gen.hp_automute_hook = alc283_hp_automute_hook;
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* MIC2-VREF control */
+ /* Set to manual mode */
+ alc_update_coef_idx(codec, 0x06, 0x000c, 0);
+ break;
+ }
+}
+
+/* mute tablet speaker pin (0x14) via dock plugging in addition */
+static void asus_tx300_automute(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ snd_hda_gen_update_outputs(codec);
+ if (snd_hda_jack_detect(codec, 0x1b))
+ spec->gen.mute_bits |= (1ULL << 0x14);
+}
+
+static void alc282_fixup_asus_tx300(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ static const struct hda_pintbl dock_pins[] = {
+ { 0x1b, 0x21114000 }, /* dock speaker pin */
+ {}
+ };
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->init_amp = ALC_INIT_DEFAULT;
+ /* TX300 needs to set up GPIO2 for the speaker amp */
+ alc_setup_gpio(codec, 0x04);
+ snd_hda_apply_pincfgs(codec, dock_pins);
+ spec->gen.auto_mute_via_amp = 1;
+ spec->gen.automute_hook = asus_tx300_automute;
+ snd_hda_jack_detect_enable_callback(codec, 0x1b,
+ snd_hda_gen_hp_automute);
+ break;
+ case HDA_FIXUP_ACT_PROBE:
+ spec->init_amp = ALC_INIT_DEFAULT;
+ break;
+ case HDA_FIXUP_ACT_BUILD:
+ /* this is a bit tricky; give more sane names for the main
+ * (tablet) speaker and the dock speaker, respectively
+ */
+ rename_ctl(codec, "Speaker Playback Switch",
+ "Dock Speaker Playback Switch");
+ rename_ctl(codec, "Bass Speaker Playback Switch",
+ "Speaker Playback Switch");
+ break;
+ }
+}
+
+static void alc290_fixup_mono_speakers(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ /* DAC node 0x03 is giving mono output. We therefore want to
+ make sure 0x14 (front speaker) and 0x15 (headphones) use the
+ stereo DAC, while leaving 0x17 (bass speaker) for node 0x03. */
+ static const hda_nid_t conn1[] = { 0x0c };
+ snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1);
+ snd_hda_override_conn_list(codec, 0x15, ARRAY_SIZE(conn1), conn1);
+ }
+}
+
+static void alc298_fixup_speaker_volume(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ /* The speaker is routed to the Node 0x06 by a mistake, as a result
+ we can't adjust the speaker's volume since this node does not has
+ Amp-out capability. we change the speaker's route to:
+ Node 0x02 (Audio Output) -> Node 0x0c (Audio Mixer) -> Node 0x17 (
+ Pin Complex), since Node 0x02 has Amp-out caps, we can adjust
+ speaker's volume now. */
+
+ static const hda_nid_t conn1[] = { 0x0c };
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn1), conn1);
+ }
+}
+
+/* disable DAC3 (0x06) selection on NID 0x17 as it has no volume amp control */
+static void alc295_fixup_disable_dac3(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ static const hda_nid_t conn[] = { 0x02, 0x03 };
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ }
+}
+
+/* force NID 0x17 (Bass Speaker) to DAC1 to share it with the main speaker */
+static void alc285_fixup_speaker2_to_dac1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ static const hda_nid_t conn[] = { 0x02 };
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ }
+}
+
+/* Hook to update amp GPIO4 for automute */
+static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+
+ snd_hda_gen_hp_automute(codec, jack);
+ /* mute_led_polarity is set to 0, so we pass inverted value here */
+ alc_update_gpio_led(codec, 0x10, spec->mute_led_polarity,
+ !spec->gen.hp_jack_present);
+}
+
+/* Manage GPIOs for HP EliteBook Folio 9480m.
+ *
+ * GPIO4 is the headphone amplifier power control
+ * GPIO3 is the audio output mute indicator LED
+ */
+
+static void alc280_fixup_hp_9480m(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_hp_gpio_led(codec, action, 0x08, 0);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ /* amp at GPIO4; toggled via alc280_hp_gpio4_automute_hook() */
+ spec->gpio_mask |= 0x10;
+ spec->gpio_dir |= 0x10;
+ spec->gen.hp_automute_hook = alc280_hp_gpio4_automute_hook;
+ }
+}
+
+static void alc275_fixup_gpio4_off(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gpio_mask |= 0x04;
+ spec->gpio_dir |= 0x04;
+ /* set data bit low */
+ }
+}
+
+/* Quirk for Thinkpad X1 7th and 8th Gen
+ * The following fixed routing needed
+ * DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly
+ * DAC2 (NID 0x03) -> Bass (NID 0x17) & Headphone (NID 0x21); sharing a DAC
+ * DAC3 (NID 0x06) -> Unused, due to the lack of volume amp
+ */
+static void alc285_fixup_thinkpad_x1_gen7(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t conn[] = { 0x02, 0x03 }; /* exclude 0x06 */
+ static const hda_nid_t preferred_pairs[] = {
+ 0x14, 0x02, 0x17, 0x03, 0x21, 0x03, 0
+ };
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ spec->gen.preferred_dacs = preferred_pairs;
+ break;
+ case HDA_FIXUP_ACT_BUILD:
+ /* The generic parser creates somewhat unintuitive volume ctls
+ * with the fixed routing above, and the shared DAC2 may be
+ * confusing for PA.
+ * Rename those to unique names so that PA doesn't touch them
+ * and use only Master volume.
+ */
+ rename_ctl(codec, "Front Playback Volume", "DAC1 Playback Volume");
+ rename_ctl(codec, "Bass Speaker Playback Volume", "DAC2 Playback Volume");
+ break;
+ }
+}
+
+static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ alc_fixup_dual_codecs(codec, fix, action);
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ /* override card longname to provide a unique UCM profile */
+ strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs");
+ break;
+ case HDA_FIXUP_ACT_BUILD:
+ /* rename Capture controls depending on the codec */
+ rename_ctl(codec, "Capture Volume",
+ codec->addr == 0 ?
+ "Rear-Panel Capture Volume" :
+ "Front-Panel Capture Volume");
+ rename_ctl(codec, "Capture Switch",
+ codec->addr == 0 ?
+ "Rear-Panel Capture Switch" :
+ "Front-Panel Capture Switch");
+ break;
+ }
+}
+
+static void alc225_fixup_s3_pop_noise(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ codec->power_save_node = 1;
+}
+
+/* Forcibly assign NID 0x03 to HP/LO while NID 0x02 to SPK for EQ */
+static void alc274_fixup_bind_dacs(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ static const hda_nid_t preferred_pairs[] = {
+ 0x21, 0x03, 0x1b, 0x03, 0x16, 0x02,
+ 0
+ };
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ spec->gen.preferred_dacs = preferred_pairs;
+ spec->gen.auto_mute_via_amp = 1;
+ codec->power_save_node = 0;
+}
+
+/* avoid DAC 0x06 for bass speaker 0x17; it has no volume control */
+static void alc289_fixup_asus_ga401(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t preferred_pairs[] = {
+ 0x14, 0x02, 0x17, 0x02, 0x21, 0x03, 0
+ };
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gen.preferred_dacs = preferred_pairs;
+ spec->gen.obey_preferred_dacs = 1;
+ }
+}
+
+/* The DAC of NID 0x3 will introduce click/pop noise on headphones, so invalidate it */
+static void alc285_fixup_invalidate_dacs(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ snd_hda_override_wcaps(codec, 0x03, 0);
+}
+
+static void alc_combo_jack_hp_jd_restart(struct hda_codec *codec)
+{
+ switch (codec->core.vendor_id) {
+ case 0x10ec0274:
+ case 0x10ec0294:
+ case 0x10ec0225:
+ case 0x10ec0295:
+ case 0x10ec0299:
+ alc_update_coef_idx(codec, 0x4a, 0x8000, 1 << 15); /* Reset HP JD */
+ alc_update_coef_idx(codec, 0x4a, 0x8000, 0 << 15);
+ break;
+ case 0x10ec0230:
+ case 0x10ec0235:
+ case 0x10ec0236:
+ case 0x10ec0255:
+ case 0x10ec0256:
+ case 0x10ec0257:
+ case 0x19e58326:
+ alc_update_coef_idx(codec, 0x1b, 0x8000, 1 << 15); /* Reset HP JD */
+ alc_update_coef_idx(codec, 0x1b, 0x8000, 0 << 15);
+ break;
+ }
+}
+
+static void alc295_fixup_chromebook(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->ultra_low_power = true;
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc_combo_jack_hp_jd_restart(codec);
+ break;
+ }
+}
+
+static void alc_fixup_disable_mic_vref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ snd_hda_codec_set_pin_target(codec, 0x19, PIN_VREFHIZ);
+}
+
+
+static void alc294_gx502_toggle_output(struct hda_codec *codec,
+ struct hda_jack_callback *cb)
+{
+ /* The Windows driver sets the codec up in a very different way where
+ * it appears to leave 0x10 = 0x8a20 set. For Linux we need to toggle it
+ */
+ if (snd_hda_jack_detect_state(codec, 0x21) == HDA_JACK_PRESENT)
+ alc_write_coef_idx(codec, 0x10, 0x8a20);
+ else
+ alc_write_coef_idx(codec, 0x10, 0x0a20);
+}
+
+static void alc294_fixup_gx502_hp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ /* Pin 0x21: headphones/headset mic */
+ if (!is_jack_detectable(codec, 0x21))
+ return;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_jack_detect_enable_callback(codec, 0x21,
+ alc294_gx502_toggle_output);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* Make sure to start in a correct state, i.e. if
+ * headphones have been plugged in before powering up the system
+ */
+ alc294_gx502_toggle_output(codec, NULL);
+ break;
+ }
+}
+
+static void alc294_gu502_toggle_output(struct hda_codec *codec,
+ struct hda_jack_callback *cb)
+{
+ /* Windows sets 0x10 to 0x8420 for Node 0x20 which is
+ * responsible from changes between speakers and headphones
+ */
+ if (snd_hda_jack_detect_state(codec, 0x21) == HDA_JACK_PRESENT)
+ alc_write_coef_idx(codec, 0x10, 0x8420);
+ else
+ alc_write_coef_idx(codec, 0x10, 0x0a20);
+}
+
+static void alc294_fixup_gu502_hp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (!is_jack_detectable(codec, 0x21))
+ return;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_jack_detect_enable_callback(codec, 0x21,
+ alc294_gu502_toggle_output);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc294_gu502_toggle_output(codec, NULL);
+ break;
+ }
+}
+
+static void alc285_fixup_hp_gpio_amp_init(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+
+ msleep(100);
+ alc_write_coef_idx(codec, 0x65, 0x0);
+}
+
+static void alc274_fixup_hp_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ switch (action) {
+ case HDA_FIXUP_ACT_INIT:
+ alc_combo_jack_hp_jd_restart(codec);
+ break;
+ }
+}
+
+static void alc_fixup_no_int_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ /* Mic RING SLEEVE swap for combo jack */
+ alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12);
+ spec->no_internal_mic_pin = true;
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc_combo_jack_hp_jd_restart(codec);
+ break;
+ }
+}
+
+/* GPIO1 = amplifier on/off
+ * GPIO3 = mic mute LED
+ */
+static void alc285_fixup_hp_spectre_x360_eb1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t conn[] = { 0x02 };
+
+ struct alc_spec *spec = codec->spec;
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x14, 0x90170110 }, /* front/high speakers */
+ { 0x17, 0x90170130 }, /* back/bass speakers */
+ { }
+ };
+
+ //enable micmute led
+ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x04);
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->micmute_led_polarity = 1;
+ /* needed for amp of back speakers */
+ spec->gpio_mask |= 0x01;
+ spec->gpio_dir |= 0x01;
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ /* share DAC to have unified volume control */
+ snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn), conn);
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* need to toggle GPIO to enable the amp of back speakers */
+ alc_update_gpio_data(codec, 0x01, true);
+ msleep(100);
+ alc_update_gpio_data(codec, 0x01, false);
+ break;
+ }
+}
+
+static void alc285_fixup_hp_spectre_x360(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const hda_nid_t conn[] = { 0x02 };
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x14, 0x90170110 }, /* rear speaker */
+ { }
+ };
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ /* force front speaker to DAC1 */
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ break;
+ }
+}
+
+/* for hda_fixup_thinkpad_acpi() */
+#include "thinkpad_helper.c"
+
+static void alc_fixup_thinkpad_acpi(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ alc_fixup_no_shutup(codec, fix, action); /* reduce click noise */
+ hda_fixup_thinkpad_acpi(codec, fix, action);
+}
+
+/* Fixup for Lenovo Legion 15IMHg05 speaker output on headset removal. */
+static void alc287_fixup_legion_15imhg05_speakers(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->gen.suppress_auto_mute = 1;
+ break;
+ }
+}
+
+/* for alc295_fixup_hp_top_speakers */
+#include "hp_x360_helper.c"
+
+/* for alc285_fixup_ideapad_s740_coef() */
+#include "ideapad_s740_helper.c"
+
+static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = {
+ WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000),
+ WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000),
+ WRITE_COEF(0x37, 0xfe05), WRITE_COEF(0x45, 0x5089),
+ {}
+};
+
+static void alc256_fixup_set_coef_defaults(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ /*
+ * A certain other OS sets these coeffs to different values. On at least
+ * one TongFang barebone these settings might survive even a cold
+ * reboot. So to restore a clean slate the values are explicitly reset
+ * to default here. Without this, the external microphone is always in a
+ * plugged-in state, while the internal microphone is always in an
+ * unplugged state, breaking the ability to use the internal microphone.
+ */
+ alc_process_coef_fw(codec, alc256_fixup_set_coef_defaults_coefs);
+}
+
+static const struct coef_fw alc233_fixup_no_audio_jack_coefs[] = {
+ WRITE_COEF(0x1a, 0x9003), WRITE_COEF(0x1b, 0x0e2b), WRITE_COEF(0x37, 0xfe06),
+ WRITE_COEF(0x38, 0x4981), WRITE_COEF(0x45, 0xd489), WRITE_COEF(0x46, 0x0074),
+ WRITE_COEF(0x49, 0x0149),
+ {}
+};
+
+static void alc233_fixup_no_audio_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ /*
+ * The audio jack input and output is not detected on the ASRock NUC Box
+ * 1100 series when cold booting without this fix. Warm rebooting from a
+ * certain other OS makes the audio functional, as COEF settings are
+ * preserved in this case. This fix sets these altered COEF values as
+ * the default.
+ */
+ alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs);
+}
+
+static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ /*
+ * The Clevo NJ51CU comes either with the ALC293 or the ALC256 codec,
+ * but uses the 0x8686 subproduct id in both cases. The ALC256 codec
+ * needs an additional quirk for sound working after suspend and resume.
+ */
+ if (codec->core.vendor_id == 0x10ec0256) {
+ alc_update_coef_idx(codec, 0x10, 1<<9, 0);
+ snd_hda_codec_set_pincfg(codec, 0x19, 0x04a11120);
+ } else {
+ snd_hda_codec_set_pincfg(codec, 0x1a, 0x04a1113c);
+ }
+}
+
+static void alc295_fixup_dell_inspiron_top_speakers(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x14, 0x90170151 },
+ { 0x17, 0x90170150 },
+ { }
+ };
+ static const hda_nid_t conn[] = { 0x02, 0x03 };
+ static const hda_nid_t preferred_pairs[] = {
+ 0x14, 0x02,
+ 0x17, 0x03,
+ 0x21, 0x02,
+ 0
+ };
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_no_shutup(codec, fix, action);
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ spec->gen.preferred_dacs = preferred_pairs;
+ break;
+ }
+}
+
+enum {
+ ALC269_FIXUP_GPIO2,
+ ALC269_FIXUP_SONY_VAIO,
+ ALC275_FIXUP_SONY_VAIO_GPIO2,
+ ALC269_FIXUP_DELL_M101Z,
+ ALC269_FIXUP_SKU_IGNORE,
+ ALC269_FIXUP_ASUS_G73JW,
+ ALC269_FIXUP_LENOVO_EAPD,
+ ALC275_FIXUP_SONY_HWEQ,
+ ALC275_FIXUP_SONY_DISABLE_AAMIX,
+ ALC271_FIXUP_DMIC,
+ ALC269_FIXUP_PCM_44K,
+ ALC269_FIXUP_STEREO_DMIC,
+ ALC269_FIXUP_HEADSET_MIC,
+ ALC269_FIXUP_QUANTA_MUTE,
+ ALC269_FIXUP_LIFEBOOK,
+ ALC269_FIXUP_LIFEBOOK_EXTMIC,
+ ALC269_FIXUP_LIFEBOOK_HP_PIN,
+ ALC269_FIXUP_LIFEBOOK_NO_HP_TO_LINEOUT,
+ ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC,
+ ALC269_FIXUP_AMIC,
+ ALC269_FIXUP_DMIC,
+ ALC269VB_FIXUP_AMIC,
+ ALC269VB_FIXUP_DMIC,
+ ALC269_FIXUP_HP_MUTE_LED,
+ ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC269_FIXUP_HP_MUTE_LED_MIC2,
+ ALC269_FIXUP_HP_MUTE_LED_MIC3,
+ ALC269_FIXUP_HP_GPIO_LED,
+ ALC269_FIXUP_HP_GPIO_MIC1_LED,
+ ALC269_FIXUP_HP_LINE1_MIC1_LED,
+ ALC269_FIXUP_INV_DMIC,
+ ALC269_FIXUP_LENOVO_DOCK,
+ ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST,
+ ALC269_FIXUP_NO_SHUTUP,
+ ALC286_FIXUP_SONY_MIC_NO_PRESENCE,
+ ALC269_FIXUP_PINCFG_NO_HP_TO_LINEOUT,
+ ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC269_FIXUP_DELL2_MIC_NO_PRESENCE,
+ ALC269_FIXUP_DELL3_MIC_NO_PRESENCE,
+ ALC269_FIXUP_DELL4_MIC_NO_PRESENCE,
+ ALC269_FIXUP_HEADSET_MODE,
+ ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC,
+ ALC269_FIXUP_ASPIRE_HEADSET_MIC,
+ ALC269_FIXUP_ASUS_X101_FUNC,
+ ALC269_FIXUP_ASUS_X101_VERB,
+ ALC269_FIXUP_ASUS_X101,
+ ALC271_FIXUP_AMIC_MIC2,
+ ALC271_FIXUP_HP_GATE_MIC_JACK,
+ ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572,
+ ALC269_FIXUP_ACER_AC700,
+ ALC269_FIXUP_LIMIT_INT_MIC_BOOST,
+ ALC269VB_FIXUP_ASUS_ZENBOOK,
+ ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A,
+ ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE,
+ ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED,
+ ALC269VB_FIXUP_ORDISSIMO_EVE2,
+ ALC283_FIXUP_CHROME_BOOK,
+ ALC283_FIXUP_SENSE_COMBO_JACK,
+ ALC282_FIXUP_ASUS_TX300,
+ ALC283_FIXUP_INT_MIC,
+ ALC290_FIXUP_MONO_SPEAKERS,
+ ALC290_FIXUP_MONO_SPEAKERS_HSJACK,
+ ALC290_FIXUP_SUBWOOFER,
+ ALC290_FIXUP_SUBWOOFER_HSJACK,
+ ALC269_FIXUP_THINKPAD_ACPI,
+ ALC269_FIXUP_DMIC_THINKPAD_ACPI,
+ ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO,
+ ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
+ ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
+ ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
+ ALC255_FIXUP_HEADSET_MODE,
+ ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC,
+ ALC293_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC292_FIXUP_TPT440_DOCK,
+ ALC292_FIXUP_TPT440,
+ ALC283_FIXUP_HEADSET_MIC,
+ ALC255_FIXUP_MIC_MUTE_LED,
+ ALC282_FIXUP_ASPIRE_V5_PINS,
+ ALC269VB_FIXUP_ASPIRE_E1_COEF,
+ ALC280_FIXUP_HP_GPIO4,
+ ALC286_FIXUP_HP_GPIO_LED,
+ ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY,
+ ALC280_FIXUP_HP_DOCK_PINS,
+ ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED,
+ ALC280_FIXUP_HP_9480M,
+ ALC245_FIXUP_HP_X360_AMP,
+ ALC285_FIXUP_HP_SPECTRE_X360_EB1,
+ ALC288_FIXUP_DELL_HEADSET_MODE,
+ ALC288_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC288_FIXUP_DELL_XPS_13,
+ ALC288_FIXUP_DISABLE_AAMIX,
+ ALC292_FIXUP_DELL_E7X_AAMIX,
+ ALC292_FIXUP_DELL_E7X,
+ ALC292_FIXUP_DISABLE_AAMIX,
+ ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK,
+ ALC298_FIXUP_ALIENWARE_MIC_NO_PRESENCE,
+ ALC298_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE,
+ ALC275_FIXUP_DELL_XPS,
+ ALC293_FIXUP_LENOVO_SPK_NOISE,
+ ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY,
+ ALC255_FIXUP_DELL_SPK_NOISE,
+ ALC225_FIXUP_DISABLE_MIC_VREF,
+ ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC295_FIXUP_DISABLE_DAC3,
+ ALC285_FIXUP_SPEAKER2_TO_DAC1,
+ ALC280_FIXUP_HP_HEADSET_MIC,
+ ALC221_FIXUP_HP_FRONT_MIC,
+ ALC292_FIXUP_TPT460,
+ ALC298_FIXUP_SPK_VOLUME,
+ ALC298_FIXUP_LENOVO_SPK_VOLUME,
+ ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER,
+ ALC269_FIXUP_ATIV_BOOK_8,
+ ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE,
+ ALC221_FIXUP_HP_MIC_NO_PRESENCE,
+ ALC256_FIXUP_ASUS_HEADSET_MODE,
+ ALC256_FIXUP_ASUS_MIC,
+ ALC256_FIXUP_ASUS_AIO_GPIO2,
+ ALC233_FIXUP_ASUS_MIC_NO_PRESENCE,
+ ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE,
+ ALC233_FIXUP_LENOVO_MULTI_CODECS,
+ ALC233_FIXUP_ACER_HEADSET_MIC,
+ ALC294_FIXUP_LENOVO_MIC_LOCATION,
+ ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE,
+ ALC225_FIXUP_S3_POP_NOISE,
+ ALC700_FIXUP_INTEL_REFERENCE,
+ ALC274_FIXUP_DELL_BIND_DACS,
+ ALC274_FIXUP_DELL_AIO_LINEOUT_VERB,
+ ALC298_FIXUP_TPT470_DOCK_FIX,
+ ALC298_FIXUP_TPT470_DOCK,
+ ALC255_FIXUP_DUMMY_LINEOUT_VERB,
+ ALC255_FIXUP_DELL_HEADSET_MIC,
+ ALC256_FIXUP_HUAWEI_MACH_WX9_PINS,
+ ALC298_FIXUP_HUAWEI_MBX_STEREO,
+ ALC295_FIXUP_HP_X360,
+ ALC221_FIXUP_HP_HEADSET_MIC,
+ ALC285_FIXUP_LENOVO_HEADPHONE_NOISE,
+ ALC295_FIXUP_HP_AUTO_MUTE,
+ ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE,
+ ALC294_FIXUP_ASUS_MIC,
+ ALC294_FIXUP_ASUS_HEADSET_MIC,
+ ALC294_FIXUP_ASUS_SPK,
+ ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE,
+ ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE,
+ ALC255_FIXUP_ACER_HEADSET_MIC,
+ ALC295_FIXUP_CHROME_BOOK,
+ ALC225_FIXUP_HEADSET_JACK,
+ ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE,
+ ALC225_FIXUP_WYSE_AUTO_MUTE,
+ ALC225_FIXUP_WYSE_DISABLE_MIC_VREF,
+ ALC286_FIXUP_ACER_AIO_HEADSET_MIC,
+ ALC256_FIXUP_ASUS_HEADSET_MIC,
+ ALC256_FIXUP_ASUS_MIC_NO_PRESENCE,
+ ALC299_FIXUP_PREDATOR_SPK,
+ ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE,
+ ALC289_FIXUP_DELL_SPK2,
+ ALC289_FIXUP_DUAL_SPK,
+ ALC294_FIXUP_SPK2_TO_DAC1,
+ ALC294_FIXUP_ASUS_DUAL_SPK,
+ ALC285_FIXUP_THINKPAD_X1_GEN7,
+ ALC285_FIXUP_THINKPAD_HEADSET_JACK,
+ ALC294_FIXUP_ASUS_HPE,
+ ALC294_FIXUP_ASUS_COEF_1B,
+ ALC294_FIXUP_ASUS_GX502_HP,
+ ALC294_FIXUP_ASUS_GX502_PINS,
+ ALC294_FIXUP_ASUS_GX502_VERBS,
+ ALC294_FIXUP_ASUS_GU502_HP,
+ ALC294_FIXUP_ASUS_GU502_PINS,
+ ALC294_FIXUP_ASUS_GU502_VERBS,
+ ALC294_FIXUP_ASUS_G513_PINS,
+ ALC285_FIXUP_ASUS_G533Z_PINS,
+ ALC285_FIXUP_HP_GPIO_LED,
+ ALC285_FIXUP_HP_MUTE_LED,
+ ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED,
+ ALC236_FIXUP_HP_MUTE_LED_COEFBIT2,
+ ALC236_FIXUP_HP_GPIO_LED,
+ ALC236_FIXUP_HP_MUTE_LED,
+ ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF,
+ ALC298_FIXUP_SAMSUNG_AMP,
+ ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET,
+ ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET,
+ ALC295_FIXUP_ASUS_MIC_NO_PRESENCE,
+ ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS,
+ ALC269VC_FIXUP_ACER_HEADSET_MIC,
+ ALC269VC_FIXUP_ACER_MIC_NO_PRESENCE,
+ ALC289_FIXUP_ASUS_GA401,
+ ALC289_FIXUP_ASUS_GA502,
+ ALC256_FIXUP_ACER_MIC_NO_PRESENCE,
+ ALC285_FIXUP_HP_GPIO_AMP_INIT,
+ ALC269_FIXUP_CZC_B20,
+ ALC269_FIXUP_CZC_TMI,
+ ALC269_FIXUP_CZC_L101,
+ ALC269_FIXUP_LEMOTE_A1802,
+ ALC269_FIXUP_LEMOTE_A190X,
+ ALC256_FIXUP_INTEL_NUC8_RUGGED,
+ ALC233_FIXUP_INTEL_NUC8_DMIC,
+ ALC233_FIXUP_INTEL_NUC8_BOOST,
+ ALC256_FIXUP_INTEL_NUC10,
+ ALC255_FIXUP_XIAOMI_HEADSET_MIC,
+ ALC274_FIXUP_HP_MIC,
+ ALC274_FIXUP_HP_HEADSET_MIC,
+ ALC274_FIXUP_HP_ENVY_GPIO,
+ ALC256_FIXUP_ASUS_HPE,
+ ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK,
+ ALC287_FIXUP_HP_GPIO_LED,
+ ALC256_FIXUP_HP_HEADSET_MIC,
+ ALC245_FIXUP_HP_GPIO_LED,
+ ALC236_FIXUP_DELL_AIO_HEADSET_MIC,
+ ALC282_FIXUP_ACER_DISABLE_LINEOUT,
+ ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST,
+ ALC256_FIXUP_ACER_HEADSET_MIC,
+ ALC285_FIXUP_IDEAPAD_S740_COEF,
+ ALC295_FIXUP_ASUS_DACS,
+ ALC295_FIXUP_HP_OMEN,
+ ALC285_FIXUP_HP_SPECTRE_X360,
+ ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP,
+ ALC623_FIXUP_LENOVO_THINKSTATION_P340,
+ ALC255_FIXUP_ACER_HEADPHONE_AND_MIC,
+ ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST,
+ ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS,
+ ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE,
+ ALC287_FIXUP_YOGA7_14ITL_SPEAKERS,
+ ALC298_FIXUP_LENOVO_C940_DUET7,
+ ALC287_FIXUP_13S_GEN2_SPEAKERS,
+ ALC256_FIXUP_SET_COEF_DEFAULTS,
+ ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE,
+ ALC233_FIXUP_NO_AUDIO_JACK,
+ ALC256_FIXUP_MIC_NO_PRESENCE_AND_RESUME,
+ ALC285_FIXUP_LEGION_Y9000X_SPEAKERS,
+ ALC285_FIXUP_LEGION_Y9000X_AUTOMUTE,
+ ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED,
+ ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS,
+ ALC236_FIXUP_DELL_DUAL_CODECS,
+};
+
+/* A special fixup for Lenovo C940 and Yoga Duet 7;
+ * both have the very same PCI SSID, and we need to apply different fixups
+ * depending on the codec ID
+ */
+static void alc298_fixup_lenovo_c940_duet7(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ int id;
+
+ if (codec->core.vendor_id == 0x10ec0298)
+ id = ALC298_FIXUP_LENOVO_SPK_VOLUME; /* C940 */
+ else
+ id = ALC287_FIXUP_YOGA7_14ITL_SPEAKERS; /* Duet 7 */
+ __snd_hda_apply_fixup(codec, id, action, 0);
+}
+
+static const struct hda_fixup alc269_fixups[] = {
+ [ALC269_FIXUP_GPIO2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_gpio2,
+ },
+ [ALC269_FIXUP_SONY_VAIO] = {
+ .type = HDA_FIXUP_PINCTLS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x19, PIN_VREFGRD},
+ {}
+ }
+ },
+ [ALC275_FIXUP_SONY_VAIO_GPIO2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc275_fixup_gpio4_off,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_SONY_VAIO
+ },
+ [ALC269_FIXUP_DELL_M101Z] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Enables internal speaker */
+ {0x20, AC_VERB_SET_COEF_INDEX, 13},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x4040},
+ {}
+ }
+ },
+ [ALC269_FIXUP_SKU_IGNORE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_sku_ignore,
+ },
+ [ALC269_FIXUP_ASUS_G73JW] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x17, 0x99130111 }, /* subwoofer */
+ { }
+ }
+ },
+ [ALC269_FIXUP_LENOVO_EAPD] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x14, AC_VERB_SET_EAPD_BTLENABLE, 0},
+ {}
+ }
+ },
+ [ALC275_FIXUP_SONY_HWEQ] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hweq,
+ .chained = true,
+ .chain_id = ALC275_FIXUP_SONY_VAIO_GPIO2
+ },
+ [ALC275_FIXUP_SONY_DISABLE_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_SONY_VAIO
+ },
+ [ALC271_FIXUP_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc271_fixup_dmic,
+ },
+ [ALC269_FIXUP_PCM_44K] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_pcm_44k,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_QUANTA_MUTE
+ },
+ [ALC269_FIXUP_STEREO_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_stereo_dmic,
+ },
+ [ALC269_FIXUP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_headset_mic,
+ },
+ [ALC269_FIXUP_QUANTA_MUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_quanta_mute,
+ },
+ [ALC269_FIXUP_LIFEBOOK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x2101103f }, /* dock line-out */
+ { 0x1b, 0x23a11040 }, /* dock mic-in */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_QUANTA_MUTE
+ },
+ [ALC269_FIXUP_LIFEBOOK_EXTMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1903c }, /* headset mic, with jack detect */
+ { }
+ },
+ },
+ [ALC269_FIXUP_LIFEBOOK_HP_PIN] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x21, 0x0221102f }, /* HP out */
+ { }
+ },
+ },
+ [ALC269_FIXUP_LIFEBOOK_NO_HP_TO_LINEOUT] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_pincfg_no_hp_to_lineout,
+ },
+ [ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_pincfg_U7x7_headset_mic,
+ },
+ [ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x03a19020 }, /* headset mic */
+ { 0x1b, 0x90170150 }, /* speaker */
+ { }
+ },
+ },
+ [ALC269_FIXUP_AMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x0121401f }, /* HP out */
+ { 0x18, 0x01a19c20 }, /* mic */
+ { 0x19, 0x99a3092f }, /* int-mic */
+ { }
+ },
+ },
+ [ALC269_FIXUP_DMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x99a3092f }, /* int-mic */
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x0121401f }, /* HP out */
+ { 0x18, 0x01a19c20 }, /* mic */
+ { }
+ },
+ },
+ [ALC269VB_FIXUP_AMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x18, 0x01a19c20 }, /* mic */
+ { 0x19, 0x99a3092f }, /* int-mic */
+ { 0x21, 0x0121401f }, /* HP out */
+ { }
+ },
+ },
+ [ALC269VB_FIXUP_DMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x99a3092f }, /* int-mic */
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x18, 0x01a19c20 }, /* mic */
+ { 0x21, 0x0121401f }, /* HP out */
+ { }
+ },
+ },
+ [ALC269_FIXUP_HP_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_mute_led,
+ },
+ [ALC269_FIXUP_HP_MUTE_LED_MIC1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_mute_led_mic1,
+ },
+ [ALC269_FIXUP_HP_MUTE_LED_MIC2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_mute_led_mic2,
+ },
+ [ALC269_FIXUP_HP_MUTE_LED_MIC3] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_mute_led_mic3,
+ .chained = true,
+ .chain_id = ALC295_FIXUP_HP_AUTO_MUTE
+ },
+ [ALC269_FIXUP_HP_GPIO_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_gpio_led,
+ },
+ [ALC269_FIXUP_HP_GPIO_MIC1_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_gpio_mic1_led,
+ },
+ [ALC269_FIXUP_HP_LINE1_MIC1_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_hp_line1_mic1_led,
+ },
+ [ALC269_FIXUP_INV_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ },
+ [ALC269_FIXUP_NO_SHUTUP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_no_shutup,
+ },
+ [ALC269_FIXUP_LENOVO_DOCK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x23a11040 }, /* dock mic */
+ { 0x1b, 0x2121103f }, /* dock headphone */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_PINCFG_NO_HP_TO_LINEOUT
+ },
+ [ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_LENOVO_DOCK,
+ },
+ [ALC269_FIXUP_PINCFG_NO_HP_TO_LINEOUT] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_pincfg_no_hp_to_lineout,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI,
+ },
+ [ALC269_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC269_FIXUP_DELL2_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x21014020 }, /* dock line out */
+ { 0x19, 0x21a19030 }, /* dock mic */
+ { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC269_FIXUP_DELL3_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC269_FIXUP_DELL4_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1b, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC269_FIXUP_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_MIC_MUTE_LED
+ },
+ [ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_no_hp_mic,
+ },
+ [ALC269_FIXUP_ASPIRE_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* headset mic w/o jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE,
+ },
+ [ALC286_FIXUP_SONY_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC256_FIXUP_HUAWEI_MACH_WX9_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60130},
+ {0x13, 0x40000000},
+ {0x14, 0x90170110},
+ {0x18, 0x411111f0},
+ {0x19, 0x04a11040},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x90170112},
+ {0x1d, 0x40759a05},
+ {0x1e, 0x411111f0},
+ {0x21, 0x04211020},
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_MIC_MUTE_LED
+ },
+ [ALC298_FIXUP_HUAWEI_MBX_STEREO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc298_fixup_huawei_mbx_stereo,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_MIC_MUTE_LED
+ },
+ [ALC269_FIXUP_ASUS_X101_FUNC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_x101_headset_mic,
+ },
+ [ALC269_FIXUP_ASUS_X101_VERB] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x08},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x0310},
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_ASUS_X101_FUNC
+ },
+ [ALC269_FIXUP_ASUS_X101] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x04a1182c }, /* Headset mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_ASUS_X101_VERB
+ },
+ [ALC271_FIXUP_AMIC_MIC2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x19, 0x01a19c20 }, /* mic */
+ { 0x1b, 0x99a7012f }, /* int-mic */
+ { 0x21, 0x0121401f }, /* HP out */
+ { }
+ },
+ },
+ [ALC271_FIXUP_HP_GATE_MIC_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc271_hp_gate_mic_jack,
+ .chained = true,
+ .chain_id = ALC271_FIXUP_AMIC_MIC2,
+ },
+ [ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC271_FIXUP_HP_GATE_MIC_JACK,
+ },
+ [ALC269_FIXUP_ACER_AC700] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x99a3092f }, /* int-mic */
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x18, 0x03a11c20 }, /* mic */
+ { 0x1e, 0x0346101e }, /* SPDIF1 */
+ { 0x21, 0x0321101f }, /* HP out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC271_FIXUP_DMIC,
+ },
+ [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI,
+ },
+ [ALC269VB_FIXUP_ASUS_ZENBOOK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC269VB_FIXUP_DMIC,
+ },
+ [ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* class-D output amp +5dB */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x12 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2800 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC269VB_FIXUP_ASUS_ZENBOOK,
+ },
+ [ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a110f0 }, /* use as headset mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ },
+ [ALC269VB_FIXUP_ORDISSIMO_EVE2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x99a3092f }, /* int-mic */
+ { 0x18, 0x03a11d20 }, /* mic */
+ { 0x19, 0x411111f0 }, /* Unused bogus pin */
+ { }
+ },
+ },
+ [ALC283_FIXUP_CHROME_BOOK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc283_fixup_chromebook,
+ },
+ [ALC283_FIXUP_SENSE_COMBO_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc283_fixup_sense_combo_jack,
+ .chained = true,
+ .chain_id = ALC283_FIXUP_CHROME_BOOK,
+ },
+ [ALC282_FIXUP_ASUS_TX300] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc282_fixup_asus_tx300,
+ },
+ [ALC283_FIXUP_INT_MIC] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x1a},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x0011},
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST
+ },
+ [ALC290_FIXUP_SUBWOOFER_HSJACK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x17, 0x90170112 }, /* subwoofer */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK,
+ },
+ [ALC290_FIXUP_SUBWOOFER] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x17, 0x90170112 }, /* subwoofer */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC290_FIXUP_MONO_SPEAKERS,
+ },
+ [ALC290_FIXUP_MONO_SPEAKERS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc290_fixup_mono_speakers,
+ },
+ [ALC290_FIXUP_MONO_SPEAKERS_HSJACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc290_fixup_mono_speakers,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_DELL3_MIC_NO_PRESENCE,
+ },
+ [ALC269_FIXUP_THINKPAD_ACPI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_thinkpad_acpi,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_SKU_IGNORE,
+ },
+ [ALC269_FIXUP_DMIC_THINKPAD_ACPI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI,
+ },
+ [ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_HEADSET_MODE
+ },
+ [ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_HEADSET_MODE
+ },
+ [ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_HEADSET_MODE
+ },
+ [ALC255_FIXUP_DELL2_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC255_FIXUP_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_alc255,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_MIC_MUTE_LED
+ },
+ [ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_alc255_no_hp_mic,
+ },
+ [ALC293_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC292_FIXUP_TPT440_DOCK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_tpt440_dock,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST
+ },
+ [ALC292_FIXUP_TPT440] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC292_FIXUP_TPT440_DOCK,
+ },
+ [ALC283_FIXUP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x04a110f0 },
+ { },
+ },
+ },
+ [ALC255_FIXUP_MIC_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_micmute_led,
+ },
+ [ALC282_FIXUP_ASPIRE_V5_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x90a60130 },
+ { 0x14, 0x90170110 },
+ { 0x17, 0x40000008 },
+ { 0x18, 0x411111f0 },
+ { 0x19, 0x01a1913c },
+ { 0x1a, 0x411111f0 },
+ { 0x1b, 0x411111f0 },
+ { 0x1d, 0x40f89b2d },
+ { 0x1e, 0x411111f0 },
+ { 0x21, 0x0321101f },
+ { },
+ },
+ },
+ [ALC269VB_FIXUP_ASPIRE_E1_COEF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269vb_fixup_aspire_e1_coef,
+ },
+ [ALC280_FIXUP_HP_GPIO4] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc280_fixup_hp_gpio4,
+ },
+ [ALC286_FIXUP_HP_GPIO_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc286_fixup_hp_gpio_led,
+ },
+ [ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc280_fixup_hp_gpio2_mic_hotkey,
+ },
+ [ALC280_FIXUP_HP_DOCK_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x21011020 }, /* line-out */
+ { 0x1a, 0x01a1903c }, /* headset mic */
+ { 0x18, 0x2181103f }, /* line-in */
+ { },
+ },
+ .chained = true,
+ .chain_id = ALC280_FIXUP_HP_GPIO4
+ },
+ [ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x21011020 }, /* line-out */
+ { 0x18, 0x2181103f }, /* line-in */
+ { },
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HP_GPIO_MIC1_LED
+ },
+ [ALC280_FIXUP_HP_9480M] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc280_fixup_hp_9480m,
+ },
+ [ALC245_FIXUP_HP_X360_AMP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc245_fixup_hp_x360_amp,
+ .chained = true,
+ .chain_id = ALC245_FIXUP_HP_GPIO_LED
+ },
+ [ALC288_FIXUP_DELL_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_dell_alc288,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_MIC_MUTE_LED
+ },
+ [ALC288_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC288_FIXUP_DELL_HEADSET_MODE
+ },
+ [ALC288_FIXUP_DISABLE_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC288_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC288_FIXUP_DELL_XPS_13] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_dell_xps13,
+ .chained = true,
+ .chain_id = ALC288_FIXUP_DISABLE_AAMIX
+ },
+ [ALC292_FIXUP_DISABLE_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_DELL2_MIC_NO_PRESENCE
+ },
+ [ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC293_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC292_FIXUP_DELL_E7X_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_dell_xps13,
+ .chained = true,
+ .chain_id = ALC292_FIXUP_DISABLE_AAMIX
+ },
+ [ALC292_FIXUP_DELL_E7X] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_micmute_led,
+ /* micmute fixup must be applied at last */
+ .chained_before = true,
+ .chain_id = ALC292_FIXUP_DELL_E7X_AAMIX,
+ },
+ [ALC298_FIXUP_ALIENWARE_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* headset mic w/o jack detect */
+ { }
+ },
+ .chained_before = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE,
+ },
+ [ALC298_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC275_FIXUP_DELL_XPS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Enables internal speaker */
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x1f},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x00c0},
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x30},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x00b1},
+ {}
+ }
+ },
+ [ALC293_FIXUP_LENOVO_SPK_NOISE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI
+ },
+ [ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc233_fixup_lenovo_line2_mic_hotkey,
+ },
+ [ALC233_FIXUP_INTEL_NUC8_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ .chained = true,
+ .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST,
+ },
+ [ALC233_FIXUP_INTEL_NUC8_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost
+ },
+ [ALC255_FIXUP_DELL_SPK_NOISE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC225_FIXUP_DISABLE_MIC_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_mic_vref,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC225_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Disable pass-through path for FRONT 14h */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x36 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x57d7 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC225_FIXUP_DISABLE_MIC_VREF
+ },
+ [ALC280_FIXUP_HP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC,
+ },
+ [ALC221_FIXUP_HP_FRONT_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x02a19020 }, /* Front Mic */
+ { }
+ },
+ },
+ [ALC292_FIXUP_TPT460] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_tpt440_dock,
+ .chained = true,
+ .chain_id = ALC293_FIXUP_LENOVO_SPK_NOISE,
+ },
+ [ALC298_FIXUP_SPK_VOLUME] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc298_fixup_speaker_volume,
+ .chained = true,
+ .chain_id = ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE,
+ },
+ [ALC298_FIXUP_LENOVO_SPK_VOLUME] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc298_fixup_speaker_volume,
+ },
+ [ALC295_FIXUP_DISABLE_DAC3] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc295_fixup_disable_dac3,
+ },
+ [ALC285_FIXUP_SPEAKER2_TO_DAC1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_speaker2_to_dac1,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI
+ },
+ [ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x90170151 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC269_FIXUP_ATIV_BOOK_8] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_auto_mute_via_amp,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_NO_SHUTUP
+ },
+ [ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01813030 }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC221_FIXUP_HP_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC256_FIXUP_ASUS_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode,
+ },
+ [ALC256_FIXUP_ASUS_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x13, 0x90a60160 }, /* use as internal mic */
+ { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
+ },
+ [ALC256_FIXUP_ASUS_AIO_GPIO2] = {
+ .type = HDA_FIXUP_FUNC,
+ /* Set up GPIO2 for the speaker amp */
+ .v.func = alc_fixup_gpio4,
+ },
+ [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Enables internal speaker */
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x40},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x8800},
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE
+ },
+ [ALC233_FIXUP_LENOVO_MULTI_CODECS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc233_alc662_fixup_lenovo_dual_codecs,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_GPIO2
+ },
+ [ALC233_FIXUP_ACER_HEADSET_MIC] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE
+ },
+ [ALC294_FIXUP_LENOVO_MIC_LOCATION] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* Change the mic location from front to right, otherwise there are
+ two front mics with the same name, pulseaudio can't handle them.
+ This is just a temporary workaround, after applying this fixup,
+ there will be one "Front Mic" and one "Mic" in this machine.
+ */
+ { 0x1a, 0x04a19040 },
+ { }
+ },
+ },
+ [ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x0101102f }, /* Rear Headset HP */
+ { 0x19, 0x02a1913c }, /* use as Front headset mic, without its own jack detect */
+ { 0x1a, 0x01a19030 }, /* Rear Headset MIC */
+ { 0x1b, 0x02011020 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC225_FIXUP_S3_POP_NOISE
+ },
+ [ALC225_FIXUP_S3_POP_NOISE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc225_fixup_s3_pop_noise,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC700_FIXUP_INTEL_REFERENCE] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Enables internal speaker */
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x45},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x5289},
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x4A},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x001b},
+ {0x58, AC_VERB_SET_COEF_INDEX, 0x00},
+ {0x58, AC_VERB_SET_PROC_COEF, 0x3888},
+ {0x20, AC_VERB_SET_COEF_INDEX, 0x6f},
+ {0x20, AC_VERB_SET_PROC_COEF, 0x2c0b},
+ {}
+ }
+ },
+ [ALC274_FIXUP_DELL_BIND_DACS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc274_fixup_bind_dacs,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC274_FIXUP_DELL_AIO_LINEOUT_VERB] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x0401102f },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC274_FIXUP_DELL_BIND_DACS
+ },
+ [ALC298_FIXUP_TPT470_DOCK_FIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_tpt470_dock,
+ .chained = true,
+ .chain_id = ALC293_FIXUP_LENOVO_SPK_NOISE
+ },
+ [ALC298_FIXUP_TPT470_DOCK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_tpt470_dacs,
+ .chained = true,
+ .chain_id = ALC298_FIXUP_TPT470_DOCK_FIX
+ },
+ [ALC255_FIXUP_DUMMY_LINEOUT_VERB] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x0201101f },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC255_FIXUP_DELL_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC295_FIXUP_HP_X360] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc295_fixup_hp_top_speakers,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HP_MUTE_LED_MIC3
+ },
+ [ALC221_FIXUP_HP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x0181313f},
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC285_FIXUP_LENOVO_HEADPHONE_NOISE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_invalidate_dacs,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI
+ },
+ [ALC295_FIXUP_HP_AUTO_MUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_auto_mute_via_amp,
+ },
+ [ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC294_FIXUP_ASUS_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x13, 0x90a60160 }, /* use as internal mic */
+ { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC294_FIXUP_ASUS_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1103c }, /* use as headset mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC294_FIXUP_ASUS_SPK] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Set EAPD high */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x40 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x8800 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x0f },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x7774 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC
+ },
+ [ALC295_FIXUP_CHROME_BOOK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc295_fixup_chromebook,
+ .chained = true,
+ .chain_id = ALC225_FIXUP_HEADSET_JACK
+ },
+ [ALC225_FIXUP_HEADSET_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_jack,
+ },
+ [ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Disable PCBEEP-IN passthrough */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x36 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x57d7 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC285_FIXUP_LENOVO_HEADPHONE_NOISE
+ },
+ [ALC255_FIXUP_ACER_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a11130 },
+ { 0x1a, 0x90a60140 }, /* use as internal mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x01011020 }, /* Rear Line out */
+ { 0x19, 0x01a1913c }, /* use as Front headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC225_FIXUP_WYSE_AUTO_MUTE
+ },
+ [ALC225_FIXUP_WYSE_AUTO_MUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_auto_mute_via_amp,
+ .chained = true,
+ .chain_id = ALC225_FIXUP_WYSE_DISABLE_MIC_VREF
+ },
+ [ALC225_FIXUP_WYSE_DISABLE_MIC_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_mic_vref,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x4f },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x5029 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE
+ },
+ [ALC256_FIXUP_ASUS_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a11020 }, /* headset mic with jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
+ },
+ [ALC256_FIXUP_ASUS_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
+ },
+ [ALC299_FIXUP_PREDATOR_SPK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x21, 0x90170150 }, /* use as headset mic, without its own jack detect */
+ { }
+ }
+ },
+ [ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x04a11040 },
+ { 0x21, 0x04211020 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
+ },
+ [ALC289_FIXUP_DELL_SPK2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x17, 0x90170130 }, /* bass spk */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE
+ },
+ [ALC289_FIXUP_DUAL_SPK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_speaker2_to_dac1,
+ .chained = true,
+ .chain_id = ALC289_FIXUP_DELL_SPK2
+ },
+ [ALC294_FIXUP_SPK2_TO_DAC1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_speaker2_to_dac1,
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC
+ },
+ [ALC294_FIXUP_ASUS_DUAL_SPK] = {
+ .type = HDA_FIXUP_FUNC,
+ /* The GPIO must be pulled to initialize the AMP */
+ .v.func = alc_fixup_gpio4,
+ .chained = true,
+ .chain_id = ALC294_FIXUP_SPK2_TO_DAC1
+ },
+ [ALC285_FIXUP_THINKPAD_X1_GEN7] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_thinkpad_x1_gen7,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI
+ },
+ [ALC285_FIXUP_THINKPAD_HEADSET_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_jack,
+ .chained = true,
+ .chain_id = ALC285_FIXUP_THINKPAD_X1_GEN7
+ },
+ [ALC294_FIXUP_ASUS_HPE] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Set EAPD high */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x0f },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x7774 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC
+ },
+ [ALC294_FIXUP_ASUS_GX502_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a11050 }, /* front HP mic */
+ { 0x1a, 0x01a11830 }, /* rear external mic */
+ { 0x21, 0x03211020 }, /* front HP out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_GX502_VERBS
+ },
+ [ALC294_FIXUP_ASUS_GX502_VERBS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* set 0x15 to HP-OUT ctrl */
+ { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* unmute the 0x15 amp */
+ { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_GX502_HP
+ },
+ [ALC294_FIXUP_ASUS_GX502_HP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc294_fixup_gx502_hp,
+ },
+ [ALC294_FIXUP_ASUS_GU502_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a11050 }, /* rear HP mic */
+ { 0x1a, 0x01a11830 }, /* rear external mic */
+ { 0x21, 0x012110f0 }, /* rear HP out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_GU502_VERBS
+ },
+ [ALC294_FIXUP_ASUS_GU502_VERBS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* set 0x15 to HP-OUT ctrl */
+ { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* unmute the 0x15 amp */
+ { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
+ /* set 0x1b to HP-OUT */
+ { 0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_GU502_HP
+ },
+ [ALC294_FIXUP_ASUS_GU502_HP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc294_fixup_gu502_hp,
+ },
+ [ALC294_FIXUP_ASUS_G513_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a11050 }, /* front HP mic */
+ { 0x1a, 0x03a11c30 }, /* rear external mic */
+ { 0x21, 0x03211420 }, /* front HP out */
+ { }
+ },
+ },
+ [ALC285_FIXUP_ASUS_G533Z_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x90170152 }, /* Speaker Surround Playback Switch */
+ { 0x19, 0x03a19020 }, /* Mic Boost Volume */
+ { 0x1a, 0x03a11c30 }, /* Mic Boost Volume */
+ { 0x1e, 0x90170151 }, /* Rear jack, IN OUT EAPD Detect */
+ { 0x21, 0x03211420 },
+ { }
+ },
+ },
+ [ALC294_FIXUP_ASUS_COEF_1B] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Set bit 10 to correct noisy output after reboot from
+ * Windows 10 (due to pop noise reduction?)
+ */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC289_FIXUP_ASUS_GA401,
+ },
+ [ALC285_FIXUP_HP_GPIO_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_hp_gpio_led,
+ },
+ [ALC285_FIXUP_HP_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_hp_mute_led,
+ },
+ [ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_hp_spectre_x360_mute_led,
+ },
+ [ALC236_FIXUP_HP_MUTE_LED_COEFBIT2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc236_fixup_hp_mute_led_coefbit2,
+ },
+ [ALC236_FIXUP_HP_GPIO_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc236_fixup_hp_gpio_led,
+ },
+ [ALC236_FIXUP_HP_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc236_fixup_hp_mute_led,
+ },
+ [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc236_fixup_hp_mute_led_micmute_vref,
+ },
+ [ALC298_FIXUP_SAMSUNG_AMP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc298_fixup_samsung_amp,
+ .chained = true,
+ .chain_id = ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET
+ },
+ [ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc5 },
+ { }
+ },
+ },
+ [ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x08},
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2fcf},
+ { }
+ },
+ },
+ [ALC295_FIXUP_ASUS_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x90100120 }, /* use as internal speaker */
+ { 0x18, 0x02a111f0 }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x01011020 }, /* use as line out */
+ { },
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC269VC_FIXUP_ACER_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x02a11030 }, /* use as headset mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC269VC_FIXUP_ACER_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a11130 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MIC
+ },
+ [ALC289_FIXUP_ASUS_GA401] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc289_fixup_asus_ga401,
+ .chained = true,
+ .chain_id = ALC289_FIXUP_ASUS_GA502,
+ },
+ [ALC289_FIXUP_ASUS_GA502] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a11020 }, /* headset mic with jack detect */
+ { }
+ },
+ },
+ [ALC256_FIXUP_ACER_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x02a11120 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
+ },
+ [ALC285_FIXUP_HP_GPIO_AMP_INIT] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_hp_gpio_amp_init,
+ .chained = true,
+ .chain_id = ALC285_FIXUP_HP_GPIO_LED
+ },
+ [ALC269_FIXUP_CZC_B20] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x411111f0 },
+ { 0x14, 0x90170110 }, /* speaker */
+ { 0x15, 0x032f1020 }, /* HP out */
+ { 0x17, 0x411111f0 },
+ { 0x18, 0x03ab1040 }, /* mic */
+ { 0x19, 0xb7a7013f },
+ { 0x1a, 0x0181305f },
+ { 0x1b, 0x411111f0 },
+ { 0x1d, 0x411111f0 },
+ { 0x1e, 0x411111f0 },
+ { }
+ },
+ .chain_id = ALC269_FIXUP_DMIC,
+ },
+ [ALC269_FIXUP_CZC_TMI] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x4000c000 },
+ { 0x14, 0x90170110 }, /* speaker */
+ { 0x15, 0x0421401f }, /* HP out */
+ { 0x17, 0x411111f0 },
+ { 0x18, 0x04a19020 }, /* mic */
+ { 0x19, 0x411111f0 },
+ { 0x1a, 0x411111f0 },
+ { 0x1b, 0x411111f0 },
+ { 0x1d, 0x40448505 },
+ { 0x1e, 0x411111f0 },
+ { 0x20, 0x8000ffff },
+ { }
+ },
+ .chain_id = ALC269_FIXUP_DMIC,
+ },
+ [ALC269_FIXUP_CZC_L101] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x40000000 },
+ { 0x14, 0x01014010 }, /* speaker */
+ { 0x15, 0x411111f0 }, /* HP out */
+ { 0x16, 0x411111f0 },
+ { 0x18, 0x01a19020 }, /* mic */
+ { 0x19, 0x02a19021 },
+ { 0x1a, 0x0181302f },
+ { 0x1b, 0x0221401f },
+ { 0x1c, 0x411111f0 },
+ { 0x1d, 0x4044c601 },
+ { 0x1e, 0x411111f0 },
+ { }
+ },
+ .chain_id = ALC269_FIXUP_DMIC,
+ },
+ [ALC269_FIXUP_LEMOTE_A1802] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0x40000000 },
+ { 0x14, 0x90170110 }, /* speaker */
+ { 0x17, 0x411111f0 },
+ { 0x18, 0x03a19040 }, /* mic1 */
+ { 0x19, 0x90a70130 }, /* mic2 */
+ { 0x1a, 0x411111f0 },
+ { 0x1b, 0x411111f0 },
+ { 0x1d, 0x40489d2d },
+ { 0x1e, 0x411111f0 },
+ { 0x20, 0x0003ffff },
+ { 0x21, 0x03214020 },
+ { }
+ },
+ .chain_id = ALC269_FIXUP_DMIC,
+ },
+ [ALC269_FIXUP_LEMOTE_A190X] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x0121401f }, /* HP out */
+ { 0x18, 0x01a19c20 }, /* rear mic */
+ { 0x19, 0x99a3092f }, /* front mic */
+ { 0x1b, 0x0201401f }, /* front lineout */
+ { }
+ },
+ .chain_id = ALC269_FIXUP_DMIC,
+ },
+ [ALC256_FIXUP_INTEL_NUC8_RUGGED] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC256_FIXUP_INTEL_NUC10] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC255_FIXUP_XIAOMI_HEADSET_MIC] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC289_FIXUP_ASUS_GA502
+ },
+ [ALC274_FIXUP_HP_MIC] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 },
+ { }
+ },
+ },
+ [ALC274_FIXUP_HP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc274_fixup_hp_headset_mic,
+ .chained = true,
+ .chain_id = ALC274_FIXUP_HP_MIC
+ },
+ [ALC274_FIXUP_HP_ENVY_GPIO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc274_fixup_hp_envy_gpio,
+ },
+ [ALC256_FIXUP_ASUS_HPE] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Set EAPD high */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x0f },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x7778 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC
+ },
+ [ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_jack,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI
+ },
+ [ALC287_FIXUP_HP_GPIO_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_hp_gpio_led,
+ },
+ [ALC256_FIXUP_HP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc274_fixup_hp_headset_mic,
+ },
+ [ALC236_FIXUP_DELL_AIO_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_no_int_mic,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE
+ },
+ [ALC282_FIXUP_ACER_DISABLE_LINEOUT] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x411111f0 },
+ { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { },
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
+ },
+ [ALC256_FIXUP_ACER_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x02a1113c }, /* use as headset mic, without its own jack detect */
+ { 0x1a, 0x90a1092f }, /* use as internal mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC285_FIXUP_IDEAPAD_S740_COEF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_ideapad_s740_coef,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI,
+ },
+ [ALC295_FIXUP_ASUS_DACS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc295_fixup_asus_dacs,
+ },
+ [ALC295_FIXUP_HP_OMEN] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x12, 0xb7a60130 },
+ { 0x13, 0x40000000 },
+ { 0x14, 0x411111f0 },
+ { 0x16, 0x411111f0 },
+ { 0x17, 0x90170110 },
+ { 0x18, 0x411111f0 },
+ { 0x19, 0x02a11030 },
+ { 0x1a, 0x411111f0 },
+ { 0x1b, 0x04a19030 },
+ { 0x1d, 0x40600001 },
+ { 0x1e, 0x411111f0 },
+ { 0x21, 0x03211020 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HP_LINE1_MIC1_LED,
+ },
+ [ALC285_FIXUP_HP_SPECTRE_X360] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_hp_spectre_x360,
+ },
+ [ALC285_FIXUP_HP_SPECTRE_X360_EB1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_hp_spectre_x360_eb1
+ },
+ [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_ideapad_s740_coef,
+ .chained = true,
+ .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK,
+ },
+ [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_no_shutup,
+ .chained = true,
+ .chain_id = ALC283_FIXUP_HEADSET_MIC,
+ },
+ [ALC255_FIXUP_ACER_HEADPHONE_AND_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x21, 0x03211030 }, /* Change the Headphone location to Left */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC255_FIXUP_XIAOMI_HEADSET_MIC
+ },
+ [ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_limit_int_mic_boost,
+ .chained = true,
+ .chain_id = ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF,
+ },
+ [ALC285_FIXUP_LEGION_Y9000X_SPEAKERS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc285_fixup_ideapad_s740_coef,
+ .chained = true,
+ .chain_id = ALC285_FIXUP_LEGION_Y9000X_AUTOMUTE,
+ },
+ [ALC285_FIXUP_LEGION_Y9000X_AUTOMUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_legion_15imhg05_speakers,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI,
+ },
+ [ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = {
+ .type = HDA_FIXUP_VERBS,
+ //.v.verbs = legion_15imhg05_coefs,
+ .v.verbs = (const struct hda_verb[]) {
+ // set left speaker Legion 7i.
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x41 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x1a },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ // set right speaker Legion 7i.
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x42 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2a },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE,
+ },
+ [ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_legion_15imhg05_speakers,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE,
+ },
+ [ALC287_FIXUP_YOGA7_14ITL_SPEAKERS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ // set left speaker Yoga 7i.
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x41 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x1a },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ // set right speaker Yoga 7i.
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x46 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2a },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE,
+ },
+ [ALC298_FIXUP_LENOVO_C940_DUET7] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc298_fixup_lenovo_c940_duet7,
+ },
+ [ALC287_FIXUP_13S_GEN2_SPEAKERS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x41 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x42 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE,
+ },
+ [ALC256_FIXUP_SET_COEF_DEFAULTS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc256_fixup_set_coef_defaults,
+ },
+ [ALC245_FIXUP_HP_GPIO_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc245_fixup_hp_gpio_led,
+ },
+ [ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a11120 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC,
+ },
+ [ALC233_FIXUP_NO_AUDIO_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc233_fixup_no_audio_jack,
+ },
+ [ALC256_FIXUP_MIC_NO_PRESENCE_AND_RESUME] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc256_fixup_mic_no_presence_and_resume,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC
+ },
+ [ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x19 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x8e11 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC285_FIXUP_HP_MUTE_LED,
+ },
+ [ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc295_fixup_dell_inspiron_top_speakers,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE,
+ },
+ [ALC236_FIXUP_DELL_DUAL_CODECS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.func = alc1220_fixup_gb_dual_codecs,
+ .chained = true,
+ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+};
+
+static const struct snd_pci_quirk alc269_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1025, 0x0283, "Acer TravelMate 8371", ALC269_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x029b, "Acer 1810TZ", ALC269_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x0349, "Acer AOD260", ALC269_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x047c, "Acer AC700", ALC269_FIXUP_ACER_AC700),
+ SND_PCI_QUIRK(0x1025, 0x072d, "Acer Aspire V5-571G", ALC269_FIXUP_ASPIRE_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x0740, "Acer AO725", ALC271_FIXUP_HP_GATE_MIC_JACK),
+ SND_PCI_QUIRK(0x1025, 0x0742, "Acer AO756", ALC271_FIXUP_HP_GATE_MIC_JACK),
+ SND_PCI_QUIRK(0x1025, 0x0762, "Acer Aspire E1-472", ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572),
+ SND_PCI_QUIRK(0x1025, 0x0775, "Acer Aspire E1-572", ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572),
+ SND_PCI_QUIRK(0x1025, 0x079b, "Acer Aspire V5-573G", ALC282_FIXUP_ASPIRE_V5_PINS),
+ SND_PCI_QUIRK(0x1025, 0x080d, "Acer Aspire V5-122P", ALC269_FIXUP_ASPIRE_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x0840, "Acer Aspire E1", ALC269VB_FIXUP_ASPIRE_E1_COEF),
+ SND_PCI_QUIRK(0x1025, 0x101c, "Acer Veriton N2510G", ALC269_FIXUP_LIFEBOOK),
+ SND_PCI_QUIRK(0x1025, 0x102b, "Acer Aspire C24-860", ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1065, "Acer Aspire C20-820", ALC269VC_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x106d, "Acer Cloudbook 14", ALC283_FIXUP_CHROME_BOOK),
+ SND_PCI_QUIRK(0x1025, 0x1094, "Acer Aspire E5-575T", ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x1025, 0x1099, "Acer Aspire E5-523G", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x110e, "Acer Aspire ES1-432", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1166, "Acer Veriton N4640G", ALC269_FIXUP_LIFEBOOK),
+ SND_PCI_QUIRK(0x1025, 0x1167, "Acer Veriton N6640G", ALC269_FIXUP_LIFEBOOK),
+ SND_PCI_QUIRK(0x1025, 0x1246, "Acer Predator Helios 500", ALC299_FIXUP_PREDATOR_SPK),
+ SND_PCI_QUIRK(0x1025, 0x1247, "Acer vCopperbox", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS),
+ SND_PCI_QUIRK(0x1025, 0x1248, "Acer Veriton N4660G", ALC269VC_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1269, "Acer SWIFT SF314-54", ALC256_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x128f, "Acer Veriton Z6860G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x1290, "Acer Veriton Z4860G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x1291, "Acer Veriton Z4660G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x129c, "Acer SWIFT SF314-55", ALC256_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x129d, "Acer SWIFT SF313-51", ALC256_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1300, "Acer SWIFT SF314-56", ALC256_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1308, "Acer Aspire Z24-890", ALC286_FIXUP_ACER_AIO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x132a, "Acer TravelMate B114-21", ALC233_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x1330, "Acer TravelMate X514-51T", ALC255_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x141f, "Acer Spin SP513-54N", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC),
+ SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z),
+ SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS),
+ SND_PCI_QUIRK(0x1028, 0x05bd, "Dell Latitude E6440", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x05be, "Dell Latitude E6540", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x05ca, "Dell Latitude E7240", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x05cb, "Dell Latitude E7440", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x05da, "Dell Vostro 5460", ALC290_FIXUP_SUBWOOFER),
+ SND_PCI_QUIRK(0x1028, 0x05f4, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x05f5, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x05f6, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0615, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK),
+ SND_PCI_QUIRK(0x1028, 0x0616, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK),
+ SND_PCI_QUIRK(0x1028, 0x062c, "Dell Latitude E5550", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x062e, "Dell Latitude E7450", ALC292_FIXUP_DELL_E7X),
+ SND_PCI_QUIRK(0x1028, 0x0638, "Dell Inspiron 5439", ALC290_FIXUP_MONO_SPEAKERS_HSJACK),
+ SND_PCI_QUIRK(0x1028, 0x064a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x064b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0665, "Dell XPS 13", ALC288_FIXUP_DELL_XPS_13),
+ SND_PCI_QUIRK(0x1028, 0x0669, "Dell Optiplex 9020m", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x069a, "Dell Vostro 5480", ALC290_FIXUP_SUBWOOFER_HSJACK),
+ SND_PCI_QUIRK(0x1028, 0x06c7, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x06d9, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x06da, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x06db, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
+ SND_PCI_QUIRK(0x1028, 0x06dd, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
+ SND_PCI_QUIRK(0x1028, 0x06de, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
+ SND_PCI_QUIRK(0x1028, 0x06df, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
+ SND_PCI_QUIRK(0x1028, 0x06e0, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
+ SND_PCI_QUIRK(0x1028, 0x0706, "Dell Inspiron 7559", ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER),
+ SND_PCI_QUIRK(0x1028, 0x0725, "Dell Inspiron 3162", ALC255_FIXUP_DELL_SPK_NOISE),
+ SND_PCI_QUIRK(0x1028, 0x0738, "Dell Precision 5820", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1028, 0x075c, "Dell XPS 27 7760", ALC298_FIXUP_SPK_VOLUME),
+ SND_PCI_QUIRK(0x1028, 0x075d, "Dell AIO", ALC298_FIXUP_SPK_VOLUME),
+ SND_PCI_QUIRK(0x1028, 0x0798, "Dell Inspiron 17 7000 Gaming", ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER),
+ SND_PCI_QUIRK(0x1028, 0x07b0, "Dell Precision 7520", ALC295_FIXUP_DISABLE_DAC3),
+ SND_PCI_QUIRK(0x1028, 0x080c, "Dell WYSE", ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x084b, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB),
+ SND_PCI_QUIRK(0x1028, 0x084e, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB),
+ SND_PCI_QUIRK(0x1028, 0x0871, "Dell Precision 3630", ALC255_FIXUP_DELL_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1028, 0x0872, "Dell Precision 3630", ALC255_FIXUP_DELL_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1028, 0x0873, "Dell Precision 3930", ALC255_FIXUP_DUMMY_LINEOUT_VERB),
+ SND_PCI_QUIRK(0x1028, 0x08ad, "Dell WYSE AIO", ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x08ae, "Dell WYSE NB", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0935, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB),
+ SND_PCI_QUIRK(0x1028, 0x097d, "Dell Precision", ALC289_FIXUP_DUAL_SPK),
+ SND_PCI_QUIRK(0x1028, 0x097e, "Dell Precision", ALC289_FIXUP_DUAL_SPK),
+ SND_PCI_QUIRK(0x1028, 0x098d, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x09bf, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0a2e, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1028, 0x0a30, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1028, 0x0a58, "Dell", ALC255_FIXUP_DELL_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1028, 0x0a61, "Dell XPS 15 9510", ALC289_FIXUP_DUAL_SPK),
+ SND_PCI_QUIRK(0x1028, 0x0a62, "Dell Precision 5560", ALC289_FIXUP_DUAL_SPK),
+ SND_PCI_QUIRK(0x1028, 0x0a9d, "Dell Latitude 5430", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0a9e, "Dell Latitude 5430", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0b19, "Dell XPS 15 9520", ALC289_FIXUP_DUAL_SPK),
+ SND_PCI_QUIRK(0x1028, 0x0b1a, "Dell Precision 5570", ALC289_FIXUP_DUAL_SPK),
+ SND_PCI_QUIRK(0x1028, 0x0b37, "Dell Inspiron 16 Plus 7620 2-in-1", ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS),
+ SND_PCI_QUIRK(0x1028, 0x0b71, "Dell Inspiron 16 Plus 7620", ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS),
+ SND_PCI_QUIRK(0x1028, 0x0c03, "Dell Precision 5340", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0c19, "Dell Precision 3340", ALC236_FIXUP_DELL_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1028, 0x0c1a, "Dell Precision 3340", ALC236_FIXUP_DELL_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1028, 0x0c1b, "Dell Precision 3440", ALC236_FIXUP_DELL_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1028, 0x0c1c, "Dell Precision 3540", ALC236_FIXUP_DELL_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1028, 0x0c1d, "Dell Precision 3440", ALC236_FIXUP_DELL_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1028, 0x0c1e, "Dell Precision 3540", ALC236_FIXUP_DELL_DUAL_CODECS),
+ SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2),
+ SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2214, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x221b, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x221c, "HP EliteBook 755 G2", ALC280_FIXUP_HP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x103c, 0x2221, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2225, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2236, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2237, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2238, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2239, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x224b, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2253, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2254, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2255, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2256, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2257, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2259, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x225a, "HP", ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x225f, "HP", ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY),
+ SND_PCI_QUIRK(0x103c, 0x2260, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2263, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2264, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2265, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2268, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226a, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2271, "HP", ALC286_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x2272, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2272, "HP", ALC280_FIXUP_HP_DOCK_PINS),
+ SND_PCI_QUIRK(0x103c, 0x2273, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2273, "HP", ALC280_FIXUP_HP_DOCK_PINS),
+ SND_PCI_QUIRK(0x103c, 0x2278, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x227f, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2282, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x228b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x228e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x229e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22b2, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22b7, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22bf, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22c4, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22c5, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22c7, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22c8, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22cf, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x22db, "HP", ALC280_FIXUP_HP_9480M),
+ SND_PCI_QUIRK(0x103c, 0x22dc, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x22fb, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED),
+ SND_PCI_QUIRK(0x103c, 0x2334, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2335, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2336, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2b5e, "HP 288 Pro G2 MT", ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x802e, "HP Z240 SFF", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x802f, "HP Z240", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x8077, "HP", ALC256_FIXUP_HP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360),
+ SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC),
+ SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360),
+ SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3),
+ SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x82c0, "HP G3 mini premium", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x83b9, "HP Spectre x360", ALC269_FIXUP_HP_MUTE_LED_MIC3),
+ SND_PCI_QUIRK(0x103c, 0x841c, "HP Pavilion 15-CK0xx", ALC269_FIXUP_HP_MUTE_LED_MIC3),
+ SND_PCI_QUIRK(0x103c, 0x8497, "HP Envy x360", ALC269_FIXUP_HP_MUTE_LED_MIC3),
+ SND_PCI_QUIRK(0x103c, 0x84ae, "HP 15-db0403ng", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
+ SND_PCI_QUIRK(0x103c, 0x84da, "HP OMEN dc0019-ur", ALC295_FIXUP_HP_OMEN),
+ SND_PCI_QUIRK(0x103c, 0x84e7, "HP Pavilion 15", ALC269_FIXUP_HP_MUTE_LED_MIC3),
+ SND_PCI_QUIRK(0x103c, 0x8519, "HP Spectre x360 15-df0xxx", ALC285_FIXUP_HP_SPECTRE_X360),
+ SND_PCI_QUIRK(0x103c, 0x8537, "HP ProBook 440 G6", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF),
+ SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x86e7, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1),
+ SND_PCI_QUIRK(0x103c, 0x86e8, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1),
+ SND_PCI_QUIRK(0x103c, 0x86f9, "HP Spectre x360 13-aw0xxx", ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x8716, "HP Elite Dragonfly G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8720, "HP EliteBook x360 1040 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8724, "HP EliteBook 850 G7", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8728, "HP EliteBook 840 G7", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8729, "HP", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8730, "HP ProBook 445 G7", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF),
+ SND_PCI_QUIRK(0x103c, 0x8735, "HP ProBook 435 G7", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF),
+ SND_PCI_QUIRK(0x103c, 0x8736, "HP", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8760, "HP", ALC285_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x877a, "HP", ALC285_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x877d, "HP", ALC236_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x8780, "HP ZBook Fury 17 G7 Mobile Workstation",
+ ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8783, "HP ZBook Fury 15 G7 Mobile Workstation",
+ ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87e5, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87e7, "HP ProBook 450 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87f1, "HP ProBook 630 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87f2, "HP ProBook 640 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87f4, "HP", ALC287_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87f5, "HP", ALC287_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x87f6, "HP Spectre x360 14", ALC245_FIXUP_HP_X360_AMP),
+ SND_PCI_QUIRK(0x103c, 0x87f7, "HP Spectre x360 14", ALC245_FIXUP_HP_X360_AMP),
+ SND_PCI_QUIRK(0x103c, 0x87fe, "HP Laptop 15s-fq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
+ SND_PCI_QUIRK(0x103c, 0x8805, "HP ProBook 650 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x880d, "HP EliteBook 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8811, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1),
+ SND_PCI_QUIRK(0x103c, 0x8812, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1),
+ SND_PCI_QUIRK(0x103c, 0x881d, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
+ SND_PCI_QUIRK(0x103c, 0x8846, "HP EliteBook 850 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x884c, "HP EliteBook 840 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8862, "HP ProBook 445 G8 Notebook PC", ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x103c, 0x8863, "HP ProBook 445 G8 Notebook PC", ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x103c, 0x886d, "HP ZBook Fury 17.3 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
+ SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
+ SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK(0x103c, 0x89aa, "HP EliteBook 630 G9", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300),
+ SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK),
+ SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK),
+ SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK),
+ SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A),
+ SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK),
+ SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x1043, 0x1740, "ASUS UX430UA", ALC295_FIXUP_ASUS_DACS),
+ SND_PCI_QUIRK(0x1043, 0x17d1, "ASUS UX431FL", ALC294_FIXUP_ASUS_DUAL_SPK),
+ SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS),
+ SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE),
+ SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1982, "ASUS B1400CEPE", ALC256_FIXUP_ASUS_HPE),
+ SND_PCI_QUIRK(0x1043, 0x19ce, "ASUS B9450FA", ALC294_FIXUP_ASUS_HPE),
+ SND_PCI_QUIRK(0x1043, 0x19e1, "ASUS UX581LV", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW),
+ SND_PCI_QUIRK(0x1043, 0x1a30, "ASUS X705UD", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B),
+ SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x1043, 0x1c62, "ASUS GU603", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1c92, "ASUS ROG Strix G15", ALC285_FIXUP_ASUS_G533Z_PINS),
+ SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1d42, "ASUS Zephyrus G14 2022", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1d4e, "ASUS TM420", ALC256_FIXUP_ASUS_HPE),
+ SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502),
+ SND_PCI_QUIRK(0x1043, 0x1e51, "ASUS Zephyrus M15", ALC294_FIXUP_ASUS_GU502_PINS),
+ SND_PCI_QUIRK(0x1043, 0x1e5e, "ASUS ROG Strix G513", ALC294_FIXUP_ASUS_G513_PINS),
+ SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1f92, "ASUS ROG Flow X16", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2),
+ SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x1043, 0x83ce, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC),
+ SND_PCI_QUIRK(0x1043, 0x8516, "ASUS X101CH", ALC269_FIXUP_ASUS_X101),
+ SND_PCI_QUIRK(0x104d, 0x9073, "Sony VAIO", ALC275_FIXUP_SONY_VAIO_GPIO2),
+ SND_PCI_QUIRK(0x104d, 0x907b, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ),
+ SND_PCI_QUIRK(0x104d, 0x9084, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ),
+ SND_PCI_QUIRK(0x104d, 0x9099, "Sony VAIO S13", ALC275_FIXUP_SONY_DISABLE_AAMIX),
+ SND_PCI_QUIRK(0x104d, 0x90b5, "Sony VAIO Pro 11", ALC286_FIXUP_SONY_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x104d, 0x90b6, "Sony VAIO Pro 13", ALC286_FIXUP_SONY_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x10cf, 0x1475, "Lifebook", ALC269_FIXUP_LIFEBOOK),
+ SND_PCI_QUIRK(0x10cf, 0x159f, "Lifebook E780", ALC269_FIXUP_LIFEBOOK_NO_HP_TO_LINEOUT),
+ SND_PCI_QUIRK(0x10cf, 0x15dc, "Lifebook T731", ALC269_FIXUP_LIFEBOOK_HP_PIN),
+ SND_PCI_QUIRK(0x10cf, 0x1629, "Lifebook U7x7", ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC),
+ SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN),
+ SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC),
+ SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE),
+ SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE),
+ SND_PCI_QUIRK(0x10ec, 0x1230, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK),
+ SND_PCI_QUIRK(0x10ec, 0x124c, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK),
+ SND_PCI_QUIRK(0x10ec, 0x1252, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK),
+ SND_PCI_QUIRK(0x10ec, 0x1254, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK),
+ SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-SZ6", ALC269_FIXUP_HEADSET_MODE),
+ SND_PCI_QUIRK(0x144d, 0xc109, "Samsung Ativ book 9 (NP900X3G)", ALC269_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x144d, 0xc169, "Samsung Notebook 9 Pen (NP930SBE-K01US)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc176, "Samsung Notebook 9 Pro (NP930MBE-K04US)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc189, "Samsung Galaxy Flex Book (NT950QCG-X716)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc18a, "Samsung Galaxy Book Ion (NP930XCJ-K01US)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc1a3, "Samsung Galaxy Book Pro (NP935XDB-KC1SE)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc1a6, "Samsung Galaxy Book Pro 360 (NP930QBD)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8),
+ SND_PCI_QUIRK(0x144d, 0xc812, "Samsung Notebook Pen S (NT950SBE-X58)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc830, "Samsung Galaxy Book Ion (NT950XCJ-X716A)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc832, "Samsung Galaxy Book Flex Alpha (NP730QCJ)", ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET),
+ SND_PCI_QUIRK(0x144d, 0xca03, "Samsung Galaxy Book2 Pro 360 (NP930QED)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x144d, 0xc868, "Samsung Galaxy Book2 Pro (NP930XED)", ALC298_FIXUP_SAMSUNG_AMP),
+ SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK),
+ SND_PCI_QUIRK(0x1558, 0x1323, "Clevo N130ZU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x1325, "System76 Darter Pro (darp5)", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x1401, "Clevo L140[CZ]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x1403, "Clevo N140CU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x1404, "Clevo N150CU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x14a1, "Clevo L141MU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x4018, "Clevo NV40M[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x4019, "Clevo NV40MZ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x4020, "Clevo NV40MB", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x4041, "Clevo NV4[15]PZ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x40a1, "Clevo NL40GU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x40c1, "Clevo NL40[CZ]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x40d1, "Clevo NL41DU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x5015, "Clevo NH5[58]H[HJK]Q", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x5017, "Clevo NH7[79]H[HJK]Q", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50a3, "Clevo NJ51GU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50b3, "Clevo NK50S[BEZ]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50b6, "Clevo NK50S5", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50b8, "Clevo NK50SZ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50d5, "Clevo NP50D5", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50e1, "Clevo NH5[58]HPQ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50e2, "Clevo NH7[79]HPQ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50f0, "Clevo NH50A[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50f2, "Clevo NH50E[PR]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50f3, "Clevo NH58DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50f5, "Clevo NH55EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x50f6, "Clevo NH55DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x5101, "Clevo S510WU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x5157, "Clevo W517GU1", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x51a1, "Clevo NS50MU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x70a1, "Clevo NB70T[HJK]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x70b3, "Clevo NK70SB", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x70f2, "Clevo NH79EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x70f3, "Clevo NH77DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x70f4, "Clevo NH77EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x70f6, "Clevo NH77DPQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x7716, "Clevo NS50PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x7717, "Clevo NS70PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x7718, "Clevo L140PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x7724, "Clevo L140AU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8228, "Clevo NR40BU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8520, "Clevo NH50D[CD]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8521, "Clevo NH77D[CD]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8535, "Clevo NH50D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8536, "Clevo NH79D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8550, "System76 Gazelle (gaze14)", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8551, "System76 Gazelle (gaze14)", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8560, "System76 Gazelle (gaze14)", ALC269_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1558, 0x8561, "System76 Gazelle (gaze14)", ALC269_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1558, 0x8562, "Clevo NH[5|7][0-9]RZ[Q]", ALC269_FIXUP_DMIC),
+ SND_PCI_QUIRK(0x1558, 0x8668, "Clevo NP50B[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x866d, "Clevo NP5[05]PN[HJK]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x867c, "Clevo NP7[01]PNP", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x867d, "Clevo NP7[01]PN[HJK]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8680, "Clevo NJ50LU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8686, "Clevo NH50[CZ]U", ALC256_FIXUP_MIC_NO_PRESENCE_AND_RESUME),
+ SND_PCI_QUIRK(0x1558, 0x8a20, "Clevo NH55DCQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8a51, "Clevo NH70RCQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x8d50, "Clevo NH55RCQ-M", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x951d, "Clevo N950T[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x9600, "Clevo N960K[PR]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x961d, "Clevo N960S[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0x971d, "Clevo N970T[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xa500, "Clevo NL53RU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xa600, "Clevo NL5XNU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xb018, "Clevo NP50D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xb019, "Clevo NH77D[BE]Q", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xb022, "Clevo NH77D[DC][QW]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xc018, "Clevo NP50D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xc019, "Clevo NH77D[BE]Q", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1558, 0xc022, "Clevo NH77[DC][QW]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS),
+ SND_PCI_QUIRK(0x17aa, 0x1048, "ThinkCentre Station", ALC623_FIXUP_LENOVO_THINKSTATION_P340),
+ SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE),
+ SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE),
+ SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE),
+ SND_PCI_QUIRK(0x17aa, 0x21ca, "Thinkpad L412", ALC269_FIXUP_SKU_IGNORE),
+ SND_PCI_QUIRK(0x17aa, 0x21e9, "Thinkpad Edge 15", ALC269_FIXUP_SKU_IGNORE),
+ SND_PCI_QUIRK(0x17aa, 0x21f3, "Thinkpad T430", ALC269_FIXUP_LENOVO_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x21f6, "Thinkpad T530", ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x21fa, "Thinkpad X230", ALC269_FIXUP_LENOVO_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x21fb, "Thinkpad T430s", ALC269_FIXUP_LENOVO_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2203, "Thinkpad X230 Tablet", ALC269_FIXUP_LENOVO_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2208, "Thinkpad T431s", ALC269_FIXUP_LENOVO_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x220c, "Thinkpad T440s", ALC292_FIXUP_TPT440),
+ SND_PCI_QUIRK(0x17aa, 0x220e, "Thinkpad T440p", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2210, "Thinkpad T540p", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x222d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x222e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2231, "Thinkpad T560", ALC292_FIXUP_TPT460),
+ SND_PCI_QUIRK(0x17aa, 0x2233, "Thinkpad", ALC292_FIXUP_TPT460),
+ SND_PCI_QUIRK(0x17aa, 0x2245, "Thinkpad T470", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2246, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2247, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x2249, "Thinkpad", ALC292_FIXUP_TPT460),
+ SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK),
+ SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK),
+ SND_PCI_QUIRK(0x17aa, 0x22c1, "Thinkpad P1 Gen 3", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK),
+ SND_PCI_QUIRK(0x17aa, 0x22c2, "Thinkpad X1 Extreme Gen 3", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK),
+ SND_PCI_QUIRK(0x17aa, 0x30bb, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY),
+ SND_PCI_QUIRK(0x17aa, 0x30e2, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY),
+ SND_PCI_QUIRK(0x17aa, 0x310c, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION),
+ SND_PCI_QUIRK(0x17aa, 0x3111, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION),
+ SND_PCI_QUIRK(0x17aa, 0x312a, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION),
+ SND_PCI_QUIRK(0x17aa, 0x312f, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION),
+ SND_PCI_QUIRK(0x17aa, 0x313c, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION),
+ SND_PCI_QUIRK(0x17aa, 0x3151, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x17aa, 0x3176, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x17aa, 0x3178, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x17aa, 0x31af, "ThinkCentre Station", ALC623_FIXUP_LENOVO_THINKSTATION_P340),
+ SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7),
+ SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3820, "Yoga Duet 7 13ITL6", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3824, "Legion Y9000X 2020", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF),
+ SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3843, "Yoga 9i", ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP),
+ SND_PCI_QUIRK(0x17aa, 0x384a, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
+ SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC),
+ SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
+ SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K),
+ SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC),
+ SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x504a, "ThinkPad X260", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x504b, "Thinkpad", ALC293_FIXUP_LENOVO_SPK_NOISE),
+ SND_PCI_QUIRK(0x17aa, 0x5050, "Thinkpad T560p", ALC292_FIXUP_TPT460),
+ SND_PCI_QUIRK(0x17aa, 0x5051, "Thinkpad L460", ALC292_FIXUP_TPT460),
+ SND_PCI_QUIRK(0x17aa, 0x5053, "Thinkpad T460", ALC292_FIXUP_TPT460),
+ SND_PCI_QUIRK(0x17aa, 0x505d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD),
+ SND_PCI_QUIRK(0x17aa, 0x9e56, "Lenovo ZhaoYang CF4620Z", ALC286_FIXUP_SONY_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1849, 0x1233, "ASRock NUC Box 1100", ALC233_FIXUP_NO_AUDIO_JACK),
+ SND_PCI_QUIRK(0x1849, 0xa233, "Positivo Master C6300", ALC269_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS),
+ SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20),
+ SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI),
+ SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101),
+ SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */
+ SND_PCI_QUIRK(0x1c06, 0x2013, "Lemote A1802", ALC269_FIXUP_LEMOTE_A1802),
+ SND_PCI_QUIRK(0x1c06, 0x2015, "Lemote A190X", ALC269_FIXUP_LEMOTE_A190X),
+ SND_PCI_QUIRK(0x1c6c, 0x1251, "Positivo N14KP6-TG", ALC288_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1d05, 0x1132, "TongFang PHxTxX1", ALC256_FIXUP_SET_COEF_DEFAULTS),
+ SND_PCI_QUIRK(0x1d05, 0x1096, "TongFang GMxMRxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x1100, "TongFang GKxNRxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x1111, "TongFang GMxZGxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x1119, "TongFang GMxZGxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x1129, "TongFang GMxZGxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x1147, "TongFang GMxTGxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x115c, "TongFang GMxTGxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d05, 0x121b, "TongFang GMxAGxx", ALC269_FIXUP_NO_SHUTUP),
+ SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1d72, 0x1945, "Redmi G", ALC256_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC),
+ SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO),
+ SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC),
+ SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED),
+ SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10),
+
+#if 0
+ /* Below is a quirk table taken from the old code.
+ * Basically the device should work as is without the fixup table.
+ * If BIOS doesn't give a proper info, enable the corresponding
+ * fixup entry.
+ */
+ SND_PCI_QUIRK(0x1043, 0x8330, "ASUS Eeepc P703 P900A",
+ ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1013, "ASUS N61Da", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1143, "ASUS B53f", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1133, "ASUS UJ20ft", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1183, "ASUS K72DR", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x11b3, "ASUS K52DR", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x11e3, "ASUS U33Jc", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1273, "ASUS UL80Jt", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1283, "ASUS U53Jc", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x12b3, "ASUS N82JV", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x12d3, "ASUS N61Jv", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x13a3, "ASUS UL30Vt", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1373, "ASUS G73JX", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1383, "ASUS UJ30Jc", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x13d3, "ASUS N61JA", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1413, "ASUS UL50", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1443, "ASUS UL30", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1453, "ASUS M60Jv", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1483, "ASUS UL80", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x14f3, "ASUS F83Vf", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x14e3, "ASUS UL20", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1513, "ASUS UX30", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1593, "ASUS N51Vn", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x15a3, "ASUS N60Jv", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x15b3, "ASUS N60Dp", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x15c3, "ASUS N70De", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x15e3, "ASUS F83T", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1643, "ASUS M60J", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1653, "ASUS U50", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1693, "ASUS F50N", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS F5Q", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1723, "ASUS P80", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1743, "ASUS U80", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1773, "ASUS U20A", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x1043, 0x1883, "ASUS F81Se", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x152d, 0x1778, "Quanta ON1", ALC269_FIXUP_DMIC),
+ SND_PCI_QUIRK(0x17aa, 0x3be9, "Quanta Wistron", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_AMIC),
+ SND_PCI_QUIRK(0x17ff, 0x059a, "Quanta EL3", ALC269_FIXUP_DMIC),
+ SND_PCI_QUIRK(0x17ff, 0x059b, "Quanta JR1", ALC269_FIXUP_DMIC),
+#endif
+ {}
+};
+
+static const struct snd_pci_quirk alc269_fixup_vendor_tbl[] = {
+ SND_PCI_QUIRK_VENDOR(0x1025, "Acer Aspire", ALC271_FIXUP_DMIC),
+ SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED),
+ SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", ALC269_FIXUP_THINKPAD_ACPI),
+ SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED),
+ {}
+};
+
+static const struct hda_model_fixup alc269_fixup_models[] = {
+ {.id = ALC269_FIXUP_AMIC, .name = "laptop-amic"},
+ {.id = ALC269_FIXUP_DMIC, .name = "laptop-dmic"},
+ {.id = ALC269_FIXUP_STEREO_DMIC, .name = "alc269-dmic"},
+ {.id = ALC271_FIXUP_DMIC, .name = "alc271-dmic"},
+ {.id = ALC269_FIXUP_INV_DMIC, .name = "inv-dmic"},
+ {.id = ALC269_FIXUP_HEADSET_MIC, .name = "headset-mic"},
+ {.id = ALC269_FIXUP_HEADSET_MODE, .name = "headset-mode"},
+ {.id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC, .name = "headset-mode-no-hp-mic"},
+ {.id = ALC269_FIXUP_LENOVO_DOCK, .name = "lenovo-dock"},
+ {.id = ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST, .name = "lenovo-dock-limit-boost"},
+ {.id = ALC269_FIXUP_HP_GPIO_LED, .name = "hp-gpio-led"},
+ {.id = ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED, .name = "hp-dock-gpio-mic1-led"},
+ {.id = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "dell-headset-multi"},
+ {.id = ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, .name = "dell-headset-dock"},
+ {.id = ALC269_FIXUP_DELL3_MIC_NO_PRESENCE, .name = "dell-headset3"},
+ {.id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, .name = "dell-headset4"},
+ {.id = ALC283_FIXUP_CHROME_BOOK, .name = "alc283-dac-wcaps"},
+ {.id = ALC283_FIXUP_SENSE_COMBO_JACK, .name = "alc283-sense-combo"},
+ {.id = ALC292_FIXUP_TPT440_DOCK, .name = "tpt440-dock"},
+ {.id = ALC292_FIXUP_TPT440, .name = "tpt440"},
+ {.id = ALC292_FIXUP_TPT460, .name = "tpt460"},
+ {.id = ALC298_FIXUP_TPT470_DOCK_FIX, .name = "tpt470-dock-fix"},
+ {.id = ALC298_FIXUP_TPT470_DOCK, .name = "tpt470-dock"},
+ {.id = ALC233_FIXUP_LENOVO_MULTI_CODECS, .name = "dual-codecs"},
+ {.id = ALC700_FIXUP_INTEL_REFERENCE, .name = "alc700-ref"},
+ {.id = ALC269_FIXUP_SONY_VAIO, .name = "vaio"},
+ {.id = ALC269_FIXUP_DELL_M101Z, .name = "dell-m101z"},
+ {.id = ALC269_FIXUP_ASUS_G73JW, .name = "asus-g73jw"},
+ {.id = ALC269_FIXUP_LENOVO_EAPD, .name = "lenovo-eapd"},
+ {.id = ALC275_FIXUP_SONY_HWEQ, .name = "sony-hweq"},
+ {.id = ALC269_FIXUP_PCM_44K, .name = "pcm44k"},
+ {.id = ALC269_FIXUP_LIFEBOOK, .name = "lifebook"},
+ {.id = ALC269_FIXUP_LIFEBOOK_EXTMIC, .name = "lifebook-extmic"},
+ {.id = ALC269_FIXUP_LIFEBOOK_HP_PIN, .name = "lifebook-hp-pin"},
+ {.id = ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC, .name = "lifebook-u7x7"},
+ {.id = ALC269VB_FIXUP_AMIC, .name = "alc269vb-amic"},
+ {.id = ALC269VB_FIXUP_DMIC, .name = "alc269vb-dmic"},
+ {.id = ALC269_FIXUP_HP_MUTE_LED_MIC1, .name = "hp-mute-led-mic1"},
+ {.id = ALC269_FIXUP_HP_MUTE_LED_MIC2, .name = "hp-mute-led-mic2"},
+ {.id = ALC269_FIXUP_HP_MUTE_LED_MIC3, .name = "hp-mute-led-mic3"},
+ {.id = ALC269_FIXUP_HP_GPIO_MIC1_LED, .name = "hp-gpio-mic1"},
+ {.id = ALC269_FIXUP_HP_LINE1_MIC1_LED, .name = "hp-line1-mic1"},
+ {.id = ALC269_FIXUP_NO_SHUTUP, .name = "noshutup"},
+ {.id = ALC286_FIXUP_SONY_MIC_NO_PRESENCE, .name = "sony-nomic"},
+ {.id = ALC269_FIXUP_ASPIRE_HEADSET_MIC, .name = "aspire-headset-mic"},
+ {.id = ALC269_FIXUP_ASUS_X101, .name = "asus-x101"},
+ {.id = ALC271_FIXUP_HP_GATE_MIC_JACK, .name = "acer-ao7xx"},
+ {.id = ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, .name = "acer-aspire-e1"},
+ {.id = ALC269_FIXUP_ACER_AC700, .name = "acer-ac700"},
+ {.id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, .name = "limit-mic-boost"},
+ {.id = ALC269VB_FIXUP_ASUS_ZENBOOK, .name = "asus-zenbook"},
+ {.id = ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, .name = "asus-zenbook-ux31a"},
+ {.id = ALC269VB_FIXUP_ORDISSIMO_EVE2, .name = "ordissimo"},
+ {.id = ALC282_FIXUP_ASUS_TX300, .name = "asus-tx300"},
+ {.id = ALC283_FIXUP_INT_MIC, .name = "alc283-int-mic"},
+ {.id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK, .name = "mono-speakers"},
+ {.id = ALC290_FIXUP_SUBWOOFER_HSJACK, .name = "alc290-subwoofer"},
+ {.id = ALC269_FIXUP_THINKPAD_ACPI, .name = "thinkpad"},
+ {.id = ALC269_FIXUP_DMIC_THINKPAD_ACPI, .name = "dmic-thinkpad"},
+ {.id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE, .name = "alc255-acer"},
+ {.id = ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, .name = "alc255-asus"},
+ {.id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc255-dell1"},
+ {.id = ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, .name = "alc255-dell2"},
+ {.id = ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc293-dell1"},
+ {.id = ALC283_FIXUP_HEADSET_MIC, .name = "alc283-headset"},
+ {.id = ALC255_FIXUP_MIC_MUTE_LED, .name = "alc255-dell-mute"},
+ {.id = ALC282_FIXUP_ASPIRE_V5_PINS, .name = "aspire-v5"},
+ {.id = ALC269VB_FIXUP_ASPIRE_E1_COEF, .name = "aspire-e1-coef"},
+ {.id = ALC280_FIXUP_HP_GPIO4, .name = "hp-gpio4"},
+ {.id = ALC286_FIXUP_HP_GPIO_LED, .name = "hp-gpio-led"},
+ {.id = ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY, .name = "hp-gpio2-hotkey"},
+ {.id = ALC280_FIXUP_HP_DOCK_PINS, .name = "hp-dock-pins"},
+ {.id = ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED, .name = "hp-dock-gpio-mic"},
+ {.id = ALC280_FIXUP_HP_9480M, .name = "hp-9480m"},
+ {.id = ALC288_FIXUP_DELL_HEADSET_MODE, .name = "alc288-dell-headset"},
+ {.id = ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc288-dell1"},
+ {.id = ALC288_FIXUP_DELL_XPS_13, .name = "alc288-dell-xps13"},
+ {.id = ALC292_FIXUP_DELL_E7X, .name = "dell-e7x"},
+ {.id = ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK, .name = "alc293-dell"},
+ {.id = ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc298-dell1"},
+ {.id = ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE, .name = "alc298-dell-aio"},
+ {.id = ALC275_FIXUP_DELL_XPS, .name = "alc275-dell-xps"},
+ {.id = ALC293_FIXUP_LENOVO_SPK_NOISE, .name = "lenovo-spk-noise"},
+ {.id = ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY, .name = "lenovo-hotkey"},
+ {.id = ALC255_FIXUP_DELL_SPK_NOISE, .name = "dell-spk-noise"},
+ {.id = ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc225-dell1"},
+ {.id = ALC295_FIXUP_DISABLE_DAC3, .name = "alc295-disable-dac3"},
+ {.id = ALC285_FIXUP_SPEAKER2_TO_DAC1, .name = "alc285-speaker2-to-dac1"},
+ {.id = ALC280_FIXUP_HP_HEADSET_MIC, .name = "alc280-hp-headset"},
+ {.id = ALC221_FIXUP_HP_FRONT_MIC, .name = "alc221-hp-mic"},
+ {.id = ALC298_FIXUP_SPK_VOLUME, .name = "alc298-spk-volume"},
+ {.id = ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, .name = "dell-inspiron-7559"},
+ {.id = ALC269_FIXUP_ATIV_BOOK_8, .name = "ativ-book"},
+ {.id = ALC221_FIXUP_HP_MIC_NO_PRESENCE, .name = "alc221-hp-mic"},
+ {.id = ALC256_FIXUP_ASUS_HEADSET_MODE, .name = "alc256-asus-headset"},
+ {.id = ALC256_FIXUP_ASUS_MIC, .name = "alc256-asus-mic"},
+ {.id = ALC256_FIXUP_ASUS_AIO_GPIO2, .name = "alc256-asus-aio"},
+ {.id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, .name = "alc233-asus"},
+ {.id = ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, .name = "alc233-eapd"},
+ {.id = ALC294_FIXUP_LENOVO_MIC_LOCATION, .name = "alc294-lenovo-mic"},
+ {.id = ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, .name = "alc225-wyse"},
+ {.id = ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, .name = "alc274-dell-aio"},
+ {.id = ALC255_FIXUP_DUMMY_LINEOUT_VERB, .name = "alc255-dummy-lineout"},
+ {.id = ALC255_FIXUP_DELL_HEADSET_MIC, .name = "alc255-dell-headset"},
+ {.id = ALC295_FIXUP_HP_X360, .name = "alc295-hp-x360"},
+ {.id = ALC225_FIXUP_HEADSET_JACK, .name = "alc-headset-jack"},
+ {.id = ALC295_FIXUP_CHROME_BOOK, .name = "alc-chrome-book"},
+ {.id = ALC299_FIXUP_PREDATOR_SPK, .name = "predator-spk"},
+ {.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"},
+ {.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"},
+ {.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"},
+ {.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"},
+ {.id = ALC255_FIXUP_XIAOMI_HEADSET_MIC, .name = "alc255-xiaomi-headset"},
+ {.id = ALC274_FIXUP_HP_MIC, .name = "alc274-hp-mic-detect"},
+ {.id = ALC245_FIXUP_HP_X360_AMP, .name = "alc245-hp-x360-amp"},
+ {.id = ALC295_FIXUP_HP_OMEN, .name = "alc295-hp-omen"},
+ {.id = ALC285_FIXUP_HP_SPECTRE_X360, .name = "alc285-hp-spectre-x360"},
+ {.id = ALC285_FIXUP_HP_SPECTRE_X360_EB1, .name = "alc285-hp-spectre-x360-eb1"},
+ {.id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, .name = "alc287-ideapad-bass-spk-amp"},
+ {.id = ALC623_FIXUP_LENOVO_THINKSTATION_P340, .name = "alc623-lenovo-thinkstation-p340"},
+ {.id = ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, .name = "alc255-acer-headphone-and-mic"},
+ {.id = ALC285_FIXUP_HP_GPIO_AMP_INIT, .name = "alc285-hp-amp-init"},
+ {}
+};
+#define ALC225_STANDARD_PINS \
+ {0x21, 0x04211020}
+
+#define ALC256_STANDARD_PINS \
+ {0x12, 0x90a60140}, \
+ {0x14, 0x90170110}, \
+ {0x21, 0x02211020}
+
+#define ALC282_STANDARD_PINS \
+ {0x14, 0x90170110}
+
+#define ALC290_STANDARD_PINS \
+ {0x12, 0x99a30130}
+
+#define ALC292_STANDARD_PINS \
+ {0x14, 0x90170110}, \
+ {0x15, 0x0221401f}
+
+#define ALC295_STANDARD_PINS \
+ {0x12, 0xb7a60130}, \
+ {0x14, 0x90170110}, \
+ {0x21, 0x04211020}
+
+#define ALC298_STANDARD_PINS \
+ {0x12, 0x90a60130}, \
+ {0x21, 0x03211020}
+
+static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
+ SND_HDA_PIN_QUIRK(0x10ec0221, 0x103c, "HP Workstation", ALC221_FIXUP_HP_HEADSET_MIC,
+ {0x14, 0x01014020},
+ {0x17, 0x90170110},
+ {0x18, 0x02a11030},
+ {0x19, 0x0181303F},
+ {0x21, 0x0221102f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
+ {0x12, 0x90a601c0},
+ {0x14, 0x90171120},
+ {0x21, 0x02211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x14, 0x90170110},
+ {0x1b, 0x90a70130},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x1a, 0x90a70130},
+ {0x1b, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC225_STANDARD_PINS,
+ {0x12, 0xb7a60130},
+ {0x14, 0x901701a0}),
+ SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC225_STANDARD_PINS,
+ {0x12, 0xb7a60130},
+ {0x14, 0x901701b0}),
+ SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC225_STANDARD_PINS,
+ {0x12, 0xb7a60150},
+ {0x14, 0x901701a0}),
+ SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC225_STANDARD_PINS,
+ {0x12, 0xb7a60150},
+ {0x14, 0x901701b0}),
+ SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC225_STANDARD_PINS,
+ {0x12, 0xb7a60130},
+ {0x1b, 0x90170110}),
+ SND_HDA_PIN_QUIRK(0x10ec0233, 0x8086, "Intel NUC Skull Canyon", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x1b, 0x01111010},
+ {0x1e, 0x01451130},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY,
+ {0x12, 0x90a60140},
+ {0x14, 0x90170110},
+ {0x19, 0x02a11030},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC294_FIXUP_LENOVO_MIC_LOCATION,
+ {0x14, 0x90170110},
+ {0x19, 0x02a11030},
+ {0x1a, 0x02a11040},
+ {0x1b, 0x01014020},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC294_FIXUP_LENOVO_MIC_LOCATION,
+ {0x14, 0x90170110},
+ {0x19, 0x02a11030},
+ {0x1a, 0x02a11040},
+ {0x1b, 0x01011020},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC294_FIXUP_LENOVO_MIC_LOCATION,
+ {0x14, 0x90170110},
+ {0x19, 0x02a11020},
+ {0x1a, 0x02a11030},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC,
+ {0x21, 0x02211010}),
+ SND_HDA_PIN_QUIRK(0x10ec0236, 0x103c, "HP", ALC256_FIXUP_HP_HEADSET_MIC,
+ {0x14, 0x90170110},
+ {0x19, 0x02a11020},
+ {0x21, 0x02211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
+ {0x14, 0x90170110},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170130},
+ {0x21, 0x02211040}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60140},
+ {0x14, 0x90170110},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60160},
+ {0x14, 0x90170120},
+ {0x21, 0x02211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170110},
+ {0x1b, 0x02011020},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170110},
+ {0x1b, 0x01011020},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170130},
+ {0x1b, 0x01014020},
+ {0x21, 0x0221103f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170130},
+ {0x1b, 0x01011020},
+ {0x21, 0x0221103f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170130},
+ {0x1b, 0x02011020},
+ {0x21, 0x0221103f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170150},
+ {0x1b, 0x02011020},
+ {0x21, 0x0221105f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x14, 0x90170110},
+ {0x1b, 0x01014020},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60160},
+ {0x14, 0x90170120},
+ {0x17, 0x90170140},
+ {0x21, 0x0321102f}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60160},
+ {0x14, 0x90170130},
+ {0x21, 0x02211040}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60160},
+ {0x14, 0x90170140},
+ {0x21, 0x02211050}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60170},
+ {0x14, 0x90170120},
+ {0x21, 0x02211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60170},
+ {0x14, 0x90170130},
+ {0x21, 0x02211040}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60170},
+ {0x14, 0x90171130},
+ {0x21, 0x02211040}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60170},
+ {0x14, 0x90170140},
+ {0x21, 0x02211050}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell Inspiron 5548", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60180},
+ {0x14, 0x90170130},
+ {0x21, 0x02211040}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell Inspiron 5565", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60180},
+ {0x14, 0x90170120},
+ {0x21, 0x02211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x1b, 0x01011020},
+ {0x21, 0x02211010}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
+ {0x14, 0x90170110},
+ {0x1b, 0x90a70130},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
+ {0x14, 0x90170110},
+ {0x1b, 0x90a70130},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x12, 0x90a60130},
+ {0x14, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x12, 0x90a60130},
+ {0x14, 0x90170110},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x1a, 0x90a70130},
+ {0x1b, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x103c, "HP", ALC256_FIXUP_HP_HEADSET_MIC,
+ {0x14, 0x90170110},
+ {0x19, 0x02a11020},
+ {0x21, 0x0221101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0274, 0x103c, "HP", ALC274_FIXUP_HP_HEADSET_MIC,
+ {0x17, 0x90170110},
+ {0x19, 0x03a11030},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4,
+ {0x12, 0x90a60130},
+ {0x14, 0x90170110},
+ {0x15, 0x0421101f},
+ {0x1a, 0x04a11020}),
+ SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED,
+ {0x12, 0x90a60140},
+ {0x14, 0x90170110},
+ {0x15, 0x0421101f},
+ {0x18, 0x02811030},
+ {0x1a, 0x04a1103f},
+ {0x1b, 0x02011020}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP 15 Touchsmart", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x99a30130},
+ {0x19, 0x03a11020},
+ {0x21, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x99a30130},
+ {0x19, 0x03a11020},
+ {0x21, 0x03211040}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x99a30130},
+ {0x19, 0x03a11030},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x99a30130},
+ {0x19, 0x04a11020},
+ {0x21, 0x0421101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x90a60140},
+ {0x19, 0x04a11030},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x1025, "Acer", ALC282_FIXUP_ACER_DISABLE_LINEOUT,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x90a609c0},
+ {0x18, 0x03a11830},
+ {0x19, 0x04a19831},
+ {0x1a, 0x0481303f},
+ {0x1b, 0x04211020},
+ {0x21, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0282, 0x1025, "Acer", ALC282_FIXUP_ACER_DISABLE_LINEOUT,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x90a60940},
+ {0x18, 0x03a11830},
+ {0x19, 0x04a19831},
+ {0x1a, 0x0481303f},
+ {0x1b, 0x04211020},
+ {0x21, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x90a60130},
+ {0x21, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60160},
+ {0x14, 0x90170120},
+ {0x21, 0x02211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC282_STANDARD_PINS,
+ {0x12, 0x90a60130},
+ {0x19, 0x03a11020},
+ {0x21, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0285, 0x17aa, "Lenovo", ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE,
+ {0x12, 0x90a60130},
+ {0x14, 0x90170110},
+ {0x19, 0x04a11040},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0285, 0x17aa, "Lenovo", ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE,
+ {0x14, 0x90170110},
+ {0x19, 0x04a11040},
+ {0x1d, 0x40600001},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0285, 0x17aa, "Lenovo", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK,
+ {0x14, 0x90170110},
+ {0x19, 0x04a11040},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0287, 0x17aa, "Lenovo", ALC285_FIXUP_THINKPAD_HEADSET_JACK,
+ {0x14, 0x90170110},
+ {0x17, 0x90170111},
+ {0x19, 0x03a11030},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0286, 0x1025, "Acer", ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE,
+ {0x12, 0x90a60130},
+ {0x17, 0x90170110},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0288, 0x1028, "Dell", ALC288_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x12, 0x90a60120},
+ {0x14, 0x90170110},
+ {0x21, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x15, 0x04211040},
+ {0x18, 0x90170112},
+ {0x1a, 0x04a11020}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x15, 0x04211040},
+ {0x18, 0x90170110},
+ {0x1a, 0x04a11020}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x15, 0x0421101f},
+ {0x1a, 0x04a11020}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x15, 0x04211020},
+ {0x1a, 0x04a11040}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x14, 0x90170110},
+ {0x15, 0x04211020},
+ {0x1a, 0x04a11040}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x14, 0x90170110},
+ {0x15, 0x04211020},
+ {0x1a, 0x04a11020}),
+ SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1,
+ ALC290_STANDARD_PINS,
+ {0x14, 0x90170110},
+ {0x15, 0x0421101f},
+ {0x1a, 0x04a11020}),
+ SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE,
+ ALC292_STANDARD_PINS,
+ {0x12, 0x90a60140},
+ {0x16, 0x01014020},
+ {0x19, 0x01a19030}),
+ SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE,
+ ALC292_STANDARD_PINS,
+ {0x12, 0x90a60140},
+ {0x16, 0x01014020},
+ {0x18, 0x02a19031},
+ {0x19, 0x01a1903e}),
+ SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE,
+ ALC292_STANDARD_PINS,
+ {0x12, 0x90a60140}),
+ SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC292_STANDARD_PINS,
+ {0x13, 0x90a60140},
+ {0x16, 0x21014020},
+ {0x19, 0x21a19030}),
+ SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC292_STANDARD_PINS,
+ {0x13, 0x90a60140}),
+ SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_HPE,
+ {0x17, 0x90170110},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_MIC,
+ {0x14, 0x90170110},
+ {0x1b, 0x90a70130},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_SPK,
+ {0x12, 0x90a60130},
+ {0x17, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_SPK,
+ {0x12, 0x90a60130},
+ {0x17, 0x90170110},
+ {0x21, 0x04211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC294_FIXUP_ASUS_SPK,
+ {0x12, 0x90a60130},
+ {0x17, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x12, 0x90a60120},
+ {0x17, 0x90170110},
+ {0x21, 0x04211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x12, 0x90a60130},
+ {0x17, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE,
+ {0x12, 0x90a60130},
+ {0x17, 0x90170110},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC298_STANDARD_PINS,
+ {0x17, 0x90170110}),
+ SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC298_STANDARD_PINS,
+ {0x17, 0x90170140}),
+ SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC298_STANDARD_PINS,
+ {0x17, 0x90170150}),
+ SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_SPK_VOLUME,
+ {0x12, 0xb7a60140},
+ {0x13, 0xb7a60150},
+ {0x17, 0x90170110},
+ {0x1a, 0x03011020},
+ {0x21, 0x03211030}),
+ SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_ALIENWARE_MIC_NO_PRESENCE,
+ {0x12, 0xb7a60140},
+ {0x17, 0x90170110},
+ {0x1a, 0x03a11030},
+ {0x21, 0x03211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0299, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE,
+ ALC225_STANDARD_PINS,
+ {0x12, 0xb7a60130},
+ {0x17, 0x90170110}),
+ SND_HDA_PIN_QUIRK(0x10ec0623, 0x17aa, "Lenovo", ALC283_FIXUP_HEADSET_MIC,
+ {0x14, 0x01014010},
+ {0x17, 0x90170120},
+ {0x18, 0x02a11030},
+ {0x19, 0x02a1103f},
+ {0x21, 0x0221101f}),
+ {}
+};
+
+/* This is the fallback pin_fixup_tbl for alc269 family, to make the tbl match
+ * more machines, don't need to match all valid pins, just need to match
+ * all the pins defined in the tbl. Just because of this reason, it is possible
+ * that a single machine matches multiple tbls, so there is one limitation:
+ * at most one tbl is allowed to define for the same vendor and same codec
+ */
+static const struct snd_hda_pin_quirk alc269_fallback_pin_fixup_tbl[] = {
+ SND_HDA_PIN_QUIRK(0x10ec0289, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE,
+ {0x19, 0x40000000},
+ {0x1b, 0x40000000}),
+ SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE,
+ {0x19, 0x40000000},
+ {0x1b, 0x40000000}),
+ SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x19, 0x40000000},
+ {0x1a, 0x40000000}),
+ SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ {0x19, 0x40000000},
+ {0x1a, 0x40000000}),
+ SND_HDA_PIN_QUIRK(0x10ec0274, 0x1028, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB,
+ {0x19, 0x40000000},
+ {0x1a, 0x40000000}),
+ {}
+};
+
+static void alc269_fill_coef(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ int val;
+
+ if (spec->codec_variant != ALC269_TYPE_ALC269VB)
+ return;
+
+ if ((alc_get_coef0(codec) & 0x00ff) < 0x015) {
+ alc_write_coef_idx(codec, 0xf, 0x960b);
+ alc_write_coef_idx(codec, 0xe, 0x8817);
+ }
+
+ if ((alc_get_coef0(codec) & 0x00ff) == 0x016) {
+ alc_write_coef_idx(codec, 0xf, 0x960b);
+ alc_write_coef_idx(codec, 0xe, 0x8814);
+ }
+
+ if ((alc_get_coef0(codec) & 0x00ff) == 0x017) {
+ /* Power up output pin */
+ alc_update_coef_idx(codec, 0x04, 0, 1<<11);
+ }
+
+ if ((alc_get_coef0(codec) & 0x00ff) == 0x018) {
+ val = alc_read_coef_idx(codec, 0xd);
+ if (val != -1 && (val & 0x0c00) >> 10 != 0x1) {
+ /* Capless ramp up clock control */
+ alc_write_coef_idx(codec, 0xd, val | (1<<10));
+ }
+ val = alc_read_coef_idx(codec, 0x17);
+ if (val != -1 && (val & 0x01c0) >> 6 != 0x4) {
+ /* Class D power on reset */
+ alc_write_coef_idx(codec, 0x17, val | (1<<7));
+ }
+ }
+
+ /* HP */
+ alc_update_coef_idx(codec, 0x4, 0, 1<<11);
+}
+
+/*
+ */
+static int patch_alc269(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x0b);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->gen.shared_mic_vref_pin = 0x18;
+ codec->power_save_node = 0;
+ spec->en_3kpull_low = true;
+
+#ifdef CONFIG_PM
+ codec->patch_ops.suspend = alc269_suspend;
+ codec->patch_ops.resume = alc269_resume;
+#endif
+ spec->shutup = alc_default_shutup;
+ spec->init_hook = alc_default_init;
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0269:
+ spec->codec_variant = ALC269_TYPE_ALC269VA;
+ switch (alc_get_coef0(codec) & 0x00f0) {
+ case 0x0010:
+ if (codec->bus->pci &&
+ codec->bus->pci->subsystem_vendor == 0x1025 &&
+ spec->cdefine.platform_type == 1)
+ err = alc_codec_rename(codec, "ALC271X");
+ spec->codec_variant = ALC269_TYPE_ALC269VB;
+ break;
+ case 0x0020:
+ if (codec->bus->pci &&
+ codec->bus->pci->subsystem_vendor == 0x17aa &&
+ codec->bus->pci->subsystem_device == 0x21f3)
+ err = alc_codec_rename(codec, "ALC3202");
+ spec->codec_variant = ALC269_TYPE_ALC269VC;
+ break;
+ case 0x0030:
+ spec->codec_variant = ALC269_TYPE_ALC269VD;
+ break;
+ default:
+ alc_fix_pll_init(codec, 0x20, 0x04, 15);
+ }
+ if (err < 0)
+ goto error;
+ spec->shutup = alc269_shutup;
+ spec->init_hook = alc269_fill_coef;
+ alc269_fill_coef(codec);
+ break;
+
+ case 0x10ec0280:
+ case 0x10ec0290:
+ spec->codec_variant = ALC269_TYPE_ALC280;
+ break;
+ case 0x10ec0282:
+ spec->codec_variant = ALC269_TYPE_ALC282;
+ spec->shutup = alc282_shutup;
+ spec->init_hook = alc282_init;
+ break;
+ case 0x10ec0233:
+ case 0x10ec0283:
+ spec->codec_variant = ALC269_TYPE_ALC283;
+ spec->shutup = alc283_shutup;
+ spec->init_hook = alc283_init;
+ break;
+ case 0x10ec0284:
+ case 0x10ec0292:
+ spec->codec_variant = ALC269_TYPE_ALC284;
+ break;
+ case 0x10ec0293:
+ spec->codec_variant = ALC269_TYPE_ALC293;
+ break;
+ case 0x10ec0286:
+ case 0x10ec0288:
+ spec->codec_variant = ALC269_TYPE_ALC286;
+ break;
+ case 0x10ec0298:
+ spec->codec_variant = ALC269_TYPE_ALC298;
+ break;
+ case 0x10ec0235:
+ case 0x10ec0255:
+ spec->codec_variant = ALC269_TYPE_ALC255;
+ spec->shutup = alc256_shutup;
+ spec->init_hook = alc256_init;
+ break;
+ case 0x10ec0230:
+ case 0x10ec0236:
+ case 0x10ec0256:
+ case 0x19e58326:
+ spec->codec_variant = ALC269_TYPE_ALC256;
+ spec->shutup = alc256_shutup;
+ spec->init_hook = alc256_init;
+ spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */
+ if (codec->core.vendor_id == 0x10ec0236 &&
+ codec->bus->pci->vendor != PCI_VENDOR_ID_AMD)
+ spec->en_3kpull_low = false;
+ break;
+ case 0x10ec0257:
+ spec->codec_variant = ALC269_TYPE_ALC257;
+ spec->shutup = alc256_shutup;
+ spec->init_hook = alc256_init;
+ spec->gen.mixer_nid = 0;
+ spec->en_3kpull_low = false;
+ break;
+ case 0x10ec0215:
+ case 0x10ec0245:
+ case 0x10ec0285:
+ case 0x10ec0287:
+ case 0x10ec0289:
+ spec->codec_variant = ALC269_TYPE_ALC215;
+ spec->shutup = alc225_shutup;
+ spec->init_hook = alc225_init;
+ spec->gen.mixer_nid = 0;
+ break;
+ case 0x10ec0225:
+ case 0x10ec0295:
+ case 0x10ec0299:
+ spec->codec_variant = ALC269_TYPE_ALC225;
+ spec->shutup = alc225_shutup;
+ spec->init_hook = alc225_init;
+ spec->gen.mixer_nid = 0; /* no loopback on ALC225, ALC295 and ALC299 */
+ break;
+ case 0x10ec0234:
+ case 0x10ec0274:
+ case 0x10ec0294:
+ spec->codec_variant = ALC269_TYPE_ALC294;
+ spec->gen.mixer_nid = 0; /* ALC2x4 does not have any loopback mixer path */
+ alc_update_coef_idx(codec, 0x6b, 0x0018, (1<<4) | (1<<3)); /* UAJ MIC Vref control by verb */
+ spec->init_hook = alc294_init;
+ break;
+ case 0x10ec0300:
+ spec->codec_variant = ALC269_TYPE_ALC300;
+ spec->gen.mixer_nid = 0; /* no loopback on ALC300 */
+ break;
+ case 0x10ec0623:
+ spec->codec_variant = ALC269_TYPE_ALC623;
+ break;
+ case 0x10ec0700:
+ case 0x10ec0701:
+ case 0x10ec0703:
+ case 0x10ec0711:
+ spec->codec_variant = ALC269_TYPE_ALC700;
+ spec->gen.mixer_nid = 0; /* ALC700 does not have any loopback mixer path */
+ alc_update_coef_idx(codec, 0x4a, 1 << 15, 0); /* Combo jack auto trigger control */
+ spec->init_hook = alc294_init;
+ break;
+
+ }
+
+ if (snd_hda_codec_read(codec, 0x51, 0, AC_VERB_PARAMETERS, 0) == 0x10ec5505) {
+ spec->has_alc5505_dsp = 1;
+ spec->init_hook = alc5505_dsp_init;
+ }
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc269_fixup_models,
+ alc269_fixup_tbl, alc269_fixups);
+ /* FIXME: both TX300 and ROG Strix G17 have the same SSID, and
+ * the quirk breaks the latter (bko#214101).
+ * Clear the wrong entry.
+ */
+ if (codec->fixup_id == ALC282_FIXUP_ASUS_TX300 &&
+ codec->core.vendor_id == 0x10ec0294) {
+ codec_dbg(codec, "Clear wrong fixup for ASUS ROG Strix G17\n");
+ codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
+ }
+
+ snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups, true);
+ snd_hda_pick_pin_fixup(codec, alc269_fallback_pin_fixup_tbl, alc269_fixups, false);
+ snd_hda_pick_fixup(codec, NULL, alc269_fixup_vendor_tbl,
+ alc269_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ alc_auto_parse_customize_define(codec);
+
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x01;
+
+ /* automatic parse from the BIOS config */
+ err = alc269_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog && spec->gen.beep_nid && spec->gen.mixer_nid) {
+ err = set_beep_amp(spec, spec->gen.mixer_nid, 0x04, HDA_INPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+/*
+ * ALC861
+ */
+
+static int alc861_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc861_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc861_ssids[] = { 0x0e, 0x0f, 0x0b, 0 };
+ return alc_parse_auto_config(codec, alc861_ignore, alc861_ssids);
+}
+
+/* Pin config fixes */
+enum {
+ ALC861_FIXUP_FSC_AMILO_PI1505,
+ ALC861_FIXUP_AMP_VREF_0F,
+ ALC861_FIXUP_NO_JACK_DETECT,
+ ALC861_FIXUP_ASUS_A6RP,
+ ALC660_FIXUP_ASUS_W7J,
+};
+
+/* On some laptops, VREF of pin 0x0f is abused for controlling the main amp */
+static void alc861_fixup_asus_amp_vref_0f(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ unsigned int val;
+
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+ val = snd_hda_codec_get_pin_target(codec, 0x0f);
+ if (!(val & (AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN)))
+ val |= AC_PINCTL_IN_EN;
+ val |= AC_PINCTL_VREF_50;
+ snd_hda_set_pin_ctl(codec, 0x0f, val);
+ spec->gen.keep_vref_in_automute = 1;
+}
+
+/* suppress the jack-detection */
+static void alc_fixup_no_jack_detect(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ codec->no_jack_detect = 1;
+}
+
+static const struct hda_fixup alc861_fixups[] = {
+ [ALC861_FIXUP_FSC_AMILO_PI1505] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x0b, 0x0221101f }, /* HP */
+ { 0x0f, 0x90170310 }, /* speaker */
+ { }
+ }
+ },
+ [ALC861_FIXUP_AMP_VREF_0F] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc861_fixup_asus_amp_vref_0f,
+ },
+ [ALC861_FIXUP_NO_JACK_DETECT] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_no_jack_detect,
+ },
+ [ALC861_FIXUP_ASUS_A6RP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc861_fixup_asus_amp_vref_0f,
+ .chained = true,
+ .chain_id = ALC861_FIXUP_NO_JACK_DETECT,
+ },
+ [ALC660_FIXUP_ASUS_W7J] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* ASUS W7J needs a magic pin setup on unused NID 0x10
+ * for enabling outputs
+ */
+ {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ { }
+ },
+ }
+};
+
+static const struct snd_pci_quirk alc861_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1043, 0x1253, "ASUS W7J", ALC660_FIXUP_ASUS_W7J),
+ SND_PCI_QUIRK(0x1043, 0x1263, "ASUS Z35HL", ALC660_FIXUP_ASUS_W7J),
+ SND_PCI_QUIRK(0x1043, 0x1393, "ASUS A6Rp", ALC861_FIXUP_ASUS_A6RP),
+ SND_PCI_QUIRK_VENDOR(0x1043, "ASUS laptop", ALC861_FIXUP_AMP_VREF_0F),
+ SND_PCI_QUIRK(0x1462, 0x7254, "HP DX2200", ALC861_FIXUP_NO_JACK_DETECT),
+ SND_PCI_QUIRK_VENDOR(0x1584, "Haier/Uniwill", ALC861_FIXUP_AMP_VREF_0F),
+ SND_PCI_QUIRK(0x1734, 0x10c7, "FSC Amilo Pi1505", ALC861_FIXUP_FSC_AMILO_PI1505),
+ {}
+};
+
+/*
+ */
+static int patch_alc861(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x15);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x23;
+
+#ifdef CONFIG_PM
+ spec->power_hook = alc_power_eapd;
+#endif
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, NULL, alc861_fixup_tbl, alc861_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ /* automatic parse from the BIOS config */
+ err = alc861_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog) {
+ err = set_beep_amp(spec, 0x23, 0, HDA_OUTPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+/*
+ * ALC861-VD support
+ *
+ * Based on ALC882
+ *
+ * In addition, an independent DAC
+ */
+static int alc861vd_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc861vd_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc861vd_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ return alc_parse_auto_config(codec, alc861vd_ignore, alc861vd_ssids);
+}
+
+enum {
+ ALC660VD_FIX_ASUS_GPIO1,
+ ALC861VD_FIX_DALLAS,
+};
+
+/* exclude VREF80 */
+static void alc861vd_fixup_dallas(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ snd_hda_override_pin_caps(codec, 0x18, 0x00000734);
+ snd_hda_override_pin_caps(codec, 0x19, 0x0000073c);
+ }
+}
+
+/* reset GPIO1 */
+static void alc660vd_fixup_asus_gpio1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->gpio_mask |= 0x02;
+ alc_fixup_gpio(codec, action, 0x01);
+}
+
+static const struct hda_fixup alc861vd_fixups[] = {
+ [ALC660VD_FIX_ASUS_GPIO1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc660vd_fixup_asus_gpio1,
+ },
+ [ALC861VD_FIX_DALLAS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc861vd_fixup_dallas,
+ },
+};
+
+static const struct snd_pci_quirk alc861vd_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x103c, 0x30bf, "HP TX1000", ALC861VD_FIX_DALLAS),
+ SND_PCI_QUIRK(0x1043, 0x1339, "ASUS A7-K", ALC660VD_FIX_ASUS_GPIO1),
+ SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba L30-149", ALC861VD_FIX_DALLAS),
+ {}
+};
+
+/*
+ */
+static int patch_alc861vd(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x0b);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x23;
+
+ spec->shutup = alc_eapd_shutup;
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, NULL, alc861vd_fixup_tbl, alc861vd_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ /* automatic parse from the BIOS config */
+ err = alc861vd_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog) {
+ err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+/*
+ * ALC662 support
+ *
+ * ALC662 is almost identical with ALC880 but has cleaner and more flexible
+ * configuration. Each pin widget can choose any input DACs and a mixer.
+ * Each ADC is connected from a mixer of all inputs. This makes possible
+ * 6-channel independent captures.
+ *
+ * In addition, an independent DAC for the multi-playback (not used in this
+ * driver yet).
+ */
+
+/*
+ * BIOS auto configuration
+ */
+
+static int alc662_parse_auto_config(struct hda_codec *codec)
+{
+ static const hda_nid_t alc662_ignore[] = { 0x1d, 0 };
+ static const hda_nid_t alc663_ssids[] = { 0x15, 0x1b, 0x14, 0x21 };
+ static const hda_nid_t alc662_ssids[] = { 0x15, 0x1b, 0x14, 0 };
+ const hda_nid_t *ssids;
+
+ if (codec->core.vendor_id == 0x10ec0272 || codec->core.vendor_id == 0x10ec0663 ||
+ codec->core.vendor_id == 0x10ec0665 || codec->core.vendor_id == 0x10ec0670 ||
+ codec->core.vendor_id == 0x10ec0671)
+ ssids = alc663_ssids;
+ else
+ ssids = alc662_ssids;
+ return alc_parse_auto_config(codec, alc662_ignore, ssids);
+}
+
+static void alc272_fixup_mario(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ if (snd_hda_override_amp_caps(codec, 0x2, HDA_OUTPUT,
+ (0x3b << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x3b << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x03 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT)))
+ codec_warn(codec, "failed to override amp caps for NID 0x2\n");
+}
+
+static const struct snd_pcm_chmap_elem asus_pcm_2_1_chmaps[] = {
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+ { .channels = 4,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_NA, SNDRV_CHMAP_LFE } }, /* LFE only on right */
+ { }
+};
+
+/* override the 2.1 chmap */
+static void alc_fixup_bass_chmap(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_BUILD) {
+ struct alc_spec *spec = codec->spec;
+ spec->gen.pcm_rec[0]->stream[0].chmap = asus_pcm_2_1_chmaps;
+ }
+}
+
+/* avoid D3 for keeping GPIO up */
+static unsigned int gpio_led_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ struct alc_spec *spec = codec->spec;
+ if (nid == codec->core.afg && power_state == AC_PWRST_D3 && spec->gpio_data)
+ return AC_PWRST_D0;
+ return power_state;
+}
+
+static void alc662_fixup_led_gpio1(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ alc_fixup_hp_gpio_led(codec, action, 0x01, 0);
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mute_led_polarity = 1;
+ codec->power_filter = gpio_led_power_filter;
+ }
+}
+
+static void alc662_usi_automute_hook(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+ int vref;
+ msleep(200);
+ snd_hda_gen_hp_automute(codec, jack);
+
+ vref = spec->gen.hp_jack_present ? PIN_VREF80 : 0;
+ msleep(100);
+ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+ vref);
+}
+
+static void alc662_fixup_usi_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ spec->gen.hp_automute_hook = alc662_usi_automute_hook;
+ }
+}
+
+static void alc662_aspire_ethos_mute_speakers(struct hda_codec *codec,
+ struct hda_jack_callback *cb)
+{
+ /* surround speakers at 0x1b already get muted automatically when
+ * headphones are plugged in, but we have to mute/unmute the remaining
+ * channels manually:
+ * 0x15 - front left/front right
+ * 0x18 - front center/ LFE
+ */
+ if (snd_hda_jack_detect_state(codec, 0x1b) == HDA_JACK_PRESENT) {
+ snd_hda_set_pin_ctl_cache(codec, 0x15, 0);
+ snd_hda_set_pin_ctl_cache(codec, 0x18, 0);
+ } else {
+ snd_hda_set_pin_ctl_cache(codec, 0x15, PIN_OUT);
+ snd_hda_set_pin_ctl_cache(codec, 0x18, PIN_OUT);
+ }
+}
+
+static void alc662_fixup_aspire_ethos_hp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ /* Pin 0x1b: shared headphones jack and surround speakers */
+ if (!is_jack_detectable(codec, 0x1b))
+ return;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_jack_detect_enable_callback(codec, 0x1b,
+ alc662_aspire_ethos_mute_speakers);
+ /* subwoofer needs an extra GPIO setting to become audible */
+ alc_setup_gpio(codec, 0x02);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* Make sure to start in a correct state, i.e. if
+ * headphones have been plugged in before powering up the system
+ */
+ alc662_aspire_ethos_mute_speakers(codec, NULL);
+ break;
+ }
+}
+
+static void alc671_fixup_hp_headset_mic2(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x19, 0x02a11040 }, /* use as headset mic, with its own jack detect */
+ { 0x1b, 0x0181304f },
+ { }
+ };
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->gen.mixer_nid = 0;
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ alc_write_coef_idx(codec, 0x19, 0xa054);
+ break;
+ }
+}
+
+static void alc897_hp_automute_hook(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct alc_spec *spec = codec->spec;
+ int vref;
+
+ snd_hda_gen_hp_automute(codec, jack);
+ vref = spec->gen.hp_jack_present ? (PIN_HP | AC_PINCTL_VREF_100) : PIN_HP;
+ snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+ vref);
+}
+
+static void alc897_fixup_lenovo_headset_mic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gen.hp_automute_hook = alc897_hp_automute_hook;
+ }
+}
+
+static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ spec->gen.hp_automute_hook = alc897_hp_automute_hook;
+ }
+}
+
+static const struct coef_fw alc668_coefs[] = {
+ WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0),
+ WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80),
+ WRITE_COEF(0x08, 0x0031), WRITE_COEF(0x0a, 0x0060), WRITE_COEF(0x0b, 0x0),
+ WRITE_COEF(0x0c, 0x7cf7), WRITE_COEF(0x0d, 0x1080), WRITE_COEF(0x0e, 0x7f7f),
+ WRITE_COEF(0x0f, 0xcccc), WRITE_COEF(0x10, 0xddcc), WRITE_COEF(0x11, 0x0001),
+ WRITE_COEF(0x13, 0x0), WRITE_COEF(0x14, 0x2aa0), WRITE_COEF(0x17, 0xa940),
+ WRITE_COEF(0x19, 0x0), WRITE_COEF(0x1a, 0x0), WRITE_COEF(0x1b, 0x0),
+ WRITE_COEF(0x1c, 0x0), WRITE_COEF(0x1d, 0x0), WRITE_COEF(0x1e, 0x7418),
+ WRITE_COEF(0x1f, 0x0804), WRITE_COEF(0x20, 0x4200), WRITE_COEF(0x21, 0x0468),
+ WRITE_COEF(0x22, 0x8ccc), WRITE_COEF(0x23, 0x0250), WRITE_COEF(0x24, 0x7418),
+ WRITE_COEF(0x27, 0x0), WRITE_COEF(0x28, 0x8ccc), WRITE_COEF(0x2a, 0xff00),
+ WRITE_COEF(0x2b, 0x8000), WRITE_COEF(0xa7, 0xff00), WRITE_COEF(0xa8, 0x8000),
+ WRITE_COEF(0xaa, 0x2e17), WRITE_COEF(0xab, 0xa0c0), WRITE_COEF(0xac, 0x0),
+ WRITE_COEF(0xad, 0x0), WRITE_COEF(0xae, 0x2ac6), WRITE_COEF(0xaf, 0xa480),
+ WRITE_COEF(0xb0, 0x0), WRITE_COEF(0xb1, 0x0), WRITE_COEF(0xb2, 0x0),
+ WRITE_COEF(0xb3, 0x0), WRITE_COEF(0xb4, 0x0), WRITE_COEF(0xb5, 0x1040),
+ WRITE_COEF(0xb6, 0xd697), WRITE_COEF(0xb7, 0x902b), WRITE_COEF(0xb8, 0xd697),
+ WRITE_COEF(0xb9, 0x902b), WRITE_COEF(0xba, 0xb8ba), WRITE_COEF(0xbb, 0xaaab),
+ WRITE_COEF(0xbc, 0xaaaf), WRITE_COEF(0xbd, 0x6aaa), WRITE_COEF(0xbe, 0x1c02),
+ WRITE_COEF(0xc0, 0x00ff), WRITE_COEF(0xc1, 0x0fa6),
+ {}
+};
+
+static void alc668_restore_default_value(struct hda_codec *codec)
+{
+ alc_process_coef_fw(codec, alc668_coefs);
+}
+
+enum {
+ ALC662_FIXUP_ASPIRE,
+ ALC662_FIXUP_LED_GPIO1,
+ ALC662_FIXUP_IDEAPAD,
+ ALC272_FIXUP_MARIO,
+ ALC662_FIXUP_CZC_ET26,
+ ALC662_FIXUP_CZC_P10T,
+ ALC662_FIXUP_SKU_IGNORE,
+ ALC662_FIXUP_HP_RP5800,
+ ALC662_FIXUP_ASUS_MODE1,
+ ALC662_FIXUP_ASUS_MODE2,
+ ALC662_FIXUP_ASUS_MODE3,
+ ALC662_FIXUP_ASUS_MODE4,
+ ALC662_FIXUP_ASUS_MODE5,
+ ALC662_FIXUP_ASUS_MODE6,
+ ALC662_FIXUP_ASUS_MODE7,
+ ALC662_FIXUP_ASUS_MODE8,
+ ALC662_FIXUP_NO_JACK_DETECT,
+ ALC662_FIXUP_ZOTAC_Z68,
+ ALC662_FIXUP_INV_DMIC,
+ ALC662_FIXUP_DELL_MIC_NO_PRESENCE,
+ ALC668_FIXUP_DELL_MIC_NO_PRESENCE,
+ ALC662_FIXUP_HEADSET_MODE,
+ ALC668_FIXUP_HEADSET_MODE,
+ ALC662_FIXUP_BASS_MODE4_CHMAP,
+ ALC662_FIXUP_BASS_16,
+ ALC662_FIXUP_BASS_1A,
+ ALC662_FIXUP_BASS_CHMAP,
+ ALC668_FIXUP_AUTO_MUTE,
+ ALC668_FIXUP_DELL_DISABLE_AAMIX,
+ ALC668_FIXUP_DELL_XPS13,
+ ALC662_FIXUP_ASUS_Nx50,
+ ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE,
+ ALC668_FIXUP_ASUS_Nx51,
+ ALC668_FIXUP_MIC_COEF,
+ ALC668_FIXUP_ASUS_G751,
+ ALC891_FIXUP_HEADSET_MODE,
+ ALC891_FIXUP_DELL_MIC_NO_PRESENCE,
+ ALC662_FIXUP_ACER_VERITON,
+ ALC892_FIXUP_ASROCK_MOBO,
+ ALC662_FIXUP_USI_FUNC,
+ ALC662_FIXUP_USI_HEADSET_MODE,
+ ALC662_FIXUP_LENOVO_MULTI_CODECS,
+ ALC669_FIXUP_ACER_ASPIRE_ETHOS,
+ ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET,
+ ALC671_FIXUP_HP_HEADSET_MIC2,
+ ALC662_FIXUP_ACER_X2660G_HEADSET_MODE,
+ ALC662_FIXUP_ACER_NITRO_HEADSET_MODE,
+ ALC668_FIXUP_ASUS_NO_HEADSET_MIC,
+ ALC668_FIXUP_HEADSET_MIC,
+ ALC668_FIXUP_MIC_DET_COEF,
+ ALC897_FIXUP_LENOVO_HEADSET_MIC,
+ ALC897_FIXUP_HEADSET_MIC_PIN,
+ ALC897_FIXUP_HP_HSMIC_VERB,
+ ALC897_FIXUP_LENOVO_HEADSET_MODE,
+ ALC897_FIXUP_HEADSET_MIC_PIN2,
+ ALC897_FIXUP_UNIS_H3C_X500S,
+};
+
+static const struct hda_fixup alc662_fixups[] = {
+ [ALC662_FIXUP_ASPIRE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x99130112 }, /* subwoofer */
+ { }
+ }
+ },
+ [ALC662_FIXUP_LED_GPIO1] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc662_fixup_led_gpio1,
+ },
+ [ALC662_FIXUP_IDEAPAD] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x17, 0x99130112 }, /* subwoofer */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_LED_GPIO1,
+ },
+ [ALC272_FIXUP_MARIO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc272_fixup_mario,
+ },
+ [ALC662_FIXUP_CZC_ET26] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x12, 0x403cc000},
+ {0x14, 0x90170110}, /* speaker */
+ {0x15, 0x411111f0},
+ {0x16, 0x411111f0},
+ {0x18, 0x01a19030}, /* mic */
+ {0x19, 0x90a7013f}, /* int-mic */
+ {0x1a, 0x01014020},
+ {0x1b, 0x0121401f},
+ {0x1c, 0x411111f0},
+ {0x1d, 0x411111f0},
+ {0x1e, 0x40478e35},
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_CZC_P10T] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x14, AC_VERB_SET_EAPD_BTLENABLE, 0},
+ {}
+ }
+ },
+ [ALC662_FIXUP_SKU_IGNORE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_sku_ignore,
+ },
+ [ALC662_FIXUP_HP_RP5800] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x0221201f }, /* HP out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE1] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x18, 0x01a19c20 }, /* mic */
+ { 0x19, 0x99a3092f }, /* int-mic */
+ { 0x21, 0x0121401f }, /* HP out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x18, 0x01a19820 }, /* mic */
+ { 0x19, 0x99a3092f }, /* int-mic */
+ { 0x1b, 0x0121401f }, /* HP out */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE3] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x0121441f }, /* HP */
+ { 0x18, 0x01a19840 }, /* mic */
+ { 0x19, 0x99a3094f }, /* int-mic */
+ { 0x21, 0x01211420 }, /* HP2 */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE4] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x16, 0x99130111 }, /* speaker */
+ { 0x18, 0x01a19840 }, /* mic */
+ { 0x19, 0x99a3094f }, /* int-mic */
+ { 0x21, 0x0121441f }, /* HP */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE5] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x0121441f }, /* HP */
+ { 0x16, 0x99130111 }, /* speaker */
+ { 0x18, 0x01a19840 }, /* mic */
+ { 0x19, 0x99a3094f }, /* int-mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE6] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x15, 0x01211420 }, /* HP2 */
+ { 0x18, 0x01a19840 }, /* mic */
+ { 0x19, 0x99a3094f }, /* int-mic */
+ { 0x1b, 0x0121441f }, /* HP */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE7] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x17, 0x99130111 }, /* speaker */
+ { 0x18, 0x01a19840 }, /* mic */
+ { 0x19, 0x99a3094f }, /* int-mic */
+ { 0x1b, 0x01214020 }, /* HP */
+ { 0x21, 0x0121401f }, /* HP */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_ASUS_MODE8] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x99130110 }, /* speaker */
+ { 0x12, 0x99a30970 }, /* int-mic */
+ { 0x15, 0x01214020 }, /* HP */
+ { 0x17, 0x99130111 }, /* speaker */
+ { 0x18, 0x01a19840 }, /* mic */
+ { 0x21, 0x0121401f }, /* HP */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_SKU_IGNORE
+ },
+ [ALC662_FIXUP_NO_JACK_DETECT] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_no_jack_detect,
+ },
+ [ALC662_FIXUP_ZOTAC_Z68] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x02214020 }, /* Front HP */
+ { }
+ }
+ },
+ [ALC662_FIXUP_INV_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_inv_dmic,
+ },
+ [ALC668_FIXUP_DELL_XPS13] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_dell_xps13,
+ .chained = true,
+ .chain_id = ALC668_FIXUP_DELL_DISABLE_AAMIX
+ },
+ [ALC668_FIXUP_DELL_DISABLE_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE
+ },
+ [ALC668_FIXUP_AUTO_MUTE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_auto_mute_via_amp,
+ .chained = true,
+ .chain_id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE
+ },
+ [ALC662_FIXUP_DELL_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a1113c }, /* use as headset mic, without its own jack detect */
+ /* headphone mic by setting pin control of 0x1b (headphone out) to in + vref_50 */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_HEADSET_MODE
+ },
+ [ALC662_FIXUP_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_alc662,
+ },
+ [ALC668_FIXUP_DELL_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */
+ { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC668_FIXUP_HEADSET_MODE
+ },
+ [ALC668_FIXUP_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_alc668,
+ },
+ [ALC662_FIXUP_BASS_MODE4_CHMAP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_bass_chmap,
+ .chained = true,
+ .chain_id = ALC662_FIXUP_ASUS_MODE4
+ },
+ [ALC662_FIXUP_BASS_16] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x16, 0x80106111}, /* bass speaker */
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_BASS_CHMAP,
+ },
+ [ALC662_FIXUP_BASS_1A] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x1a, 0x80106111}, /* bass speaker */
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_BASS_CHMAP,
+ },
+ [ALC662_FIXUP_BASS_CHMAP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_bass_chmap,
+ },
+ [ALC662_FIXUP_ASUS_Nx50] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_auto_mute_via_amp,
+ .chained = true,
+ .chain_id = ALC662_FIXUP_BASS_1A
+ },
+ [ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode_alc668,
+ .chain_id = ALC662_FIXUP_BASS_CHMAP
+ },
+ [ALC668_FIXUP_ASUS_Nx51] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */
+ { 0x1a, 0x90170151 }, /* bass speaker */
+ { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE,
+ },
+ [ALC668_FIXUP_MIC_COEF] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0xc3 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x4000 },
+ {}
+ },
+ },
+ [ALC668_FIXUP_ASUS_G751] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x0421101f }, /* HP */
+ {}
+ },
+ .chained = true,
+ .chain_id = ALC668_FIXUP_MIC_COEF
+ },
+ [ALC891_FIXUP_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode,
+ },
+ [ALC891_FIXUP_DELL_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */
+ { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC891_FIXUP_HEADSET_MODE
+ },
+ [ALC662_FIXUP_ACER_VERITON] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x50170120 }, /* no internal speaker */
+ { }
+ }
+ },
+ [ALC892_FIXUP_ASROCK_MOBO] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x40f000f0 }, /* disabled */
+ { 0x16, 0x40f000f0 }, /* disabled */
+ { }
+ }
+ },
+ [ALC662_FIXUP_USI_FUNC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc662_fixup_usi_headset_mic,
+ },
+ [ALC662_FIXUP_USI_HEADSET_MODE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x02a1913c }, /* use as headset mic, without its own jack detect */
+ { 0x18, 0x01a1903d },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_USI_FUNC
+ },
+ [ALC662_FIXUP_LENOVO_MULTI_CODECS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc233_alc662_fixup_lenovo_dual_codecs,
+ },
+ [ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc662_fixup_aspire_ethos_hp,
+ },
+ [ALC669_FIXUP_ACER_ASPIRE_ETHOS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x15, 0x92130110 }, /* front speakers */
+ { 0x18, 0x99130111 }, /* center/subwoofer */
+ { 0x1b, 0x11130012 }, /* surround plus jack for HP */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET
+ },
+ [ALC671_FIXUP_HP_HEADSET_MIC2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc671_fixup_hp_headset_mic2,
+ },
+ [ALC662_FIXUP_ACER_X2660G_HEADSET_MODE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x02a1113c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_USI_FUNC
+ },
+ [ALC662_FIXUP_ACER_NITRO_HEADSET_MODE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x01a11140 }, /* use as headset mic, without its own jack detect */
+ { 0x1b, 0x0221144f },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC662_FIXUP_USI_FUNC
+ },
+ [ALC668_FIXUP_ASUS_NO_HEADSET_MIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1b, 0x04a1112c },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC668_FIXUP_HEADSET_MIC
+ },
+ [ALC668_FIXUP_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc269_fixup_headset_mic,
+ .chained = true,
+ .chain_id = ALC668_FIXUP_MIC_DET_COEF
+ },
+ [ALC668_FIXUP_MIC_DET_COEF] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x15 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0d60 },
+ {}
+ },
+ },
+ [ALC897_FIXUP_LENOVO_HEADSET_MIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc897_fixup_lenovo_headset_mic,
+ },
+ [ALC897_FIXUP_HEADSET_MIC_PIN] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x03a11050 },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC897_FIXUP_LENOVO_HEADSET_MIC
+ },
+ [ALC897_FIXUP_HP_HSMIC_VERB] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ },
+ [ALC897_FIXUP_LENOVO_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc897_fixup_lenovo_headset_mode,
+ },
+ [ALC897_FIXUP_HEADSET_MIC_PIN2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x1a, 0x01a11140 }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC897_FIXUP_LENOVO_HEADSET_MODE
+ },
+ [ALC897_FIXUP_UNIS_H3C_X500S] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ { 0x14, AC_VERB_SET_EAPD_BTLENABLE, 0 },
+ {}
+ },
+ },
+};
+
+static const struct snd_pci_quirk alc662_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1025, 0x022f, "Acer Aspire One", ALC662_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x0241, "Packard Bell DOTS", ALC662_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE),
+ SND_PCI_QUIRK(0x1025, 0x031c, "Gateway NV79", ALC662_FIXUP_SKU_IGNORE),
+ SND_PCI_QUIRK(0x1025, 0x0349, "eMachines eM250", ALC662_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x034a, "Gateway LT27", ALC662_FIXUP_INV_DMIC),
+ SND_PCI_QUIRK(0x1025, 0x038b, "Acer Aspire 8943G", ALC662_FIXUP_ASPIRE),
+ SND_PCI_QUIRK(0x1025, 0x0566, "Acer Aspire Ethos 8951G", ALC669_FIXUP_ACER_ASPIRE_ETHOS),
+ SND_PCI_QUIRK(0x1025, 0x123c, "Acer Nitro N50-600", ALC662_FIXUP_ACER_NITRO_HEADSET_MODE),
+ SND_PCI_QUIRK(0x1025, 0x124e, "Acer 2660G", ALC662_FIXUP_ACER_X2660G_HEADSET_MODE),
+ SND_PCI_QUIRK(0x1028, 0x05d8, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x05db, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x05fe, "Dell XPS 15", ALC668_FIXUP_DELL_XPS13),
+ SND_PCI_QUIRK(0x1028, 0x060a, "Dell XPS 13", ALC668_FIXUP_DELL_XPS13),
+ SND_PCI_QUIRK(0x1028, 0x060d, "Dell M3800", ALC668_FIXUP_DELL_XPS13),
+ SND_PCI_QUIRK(0x1028, 0x0625, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0626, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0696, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0698, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x069f, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x103c, 0x1632, "HP RP5800", ALC662_FIXUP_HP_RP5800),
+ SND_PCI_QUIRK(0x103c, 0x870c, "HP", ALC897_FIXUP_HP_HSMIC_VERB),
+ SND_PCI_QUIRK(0x103c, 0x8719, "HP", ALC897_FIXUP_HP_HSMIC_VERB),
+ SND_PCI_QUIRK(0x103c, 0x872b, "HP", ALC897_FIXUP_HP_HSMIC_VERB),
+ SND_PCI_QUIRK(0x103c, 0x873e, "HP", ALC671_FIXUP_HP_HEADSET_MIC2),
+ SND_PCI_QUIRK(0x103c, 0x8768, "HP Slim Desktop S01", ALC671_FIXUP_HP_HEADSET_MIC2),
+ SND_PCI_QUIRK(0x103c, 0x877e, "HP 288 Pro G6", ALC671_FIXUP_HP_HEADSET_MIC2),
+ SND_PCI_QUIRK(0x103c, 0x885f, "HP 288 Pro G8", ALC671_FIXUP_HP_HEADSET_MIC2),
+ SND_PCI_QUIRK(0x1043, 0x1080, "Asus UX501VW", ALC668_FIXUP_HEADSET_MODE),
+ SND_PCI_QUIRK(0x1043, 0x11cd, "Asus N550", ALC662_FIXUP_ASUS_Nx50),
+ SND_PCI_QUIRK(0x1043, 0x129d, "Asus N750", ALC662_FIXUP_ASUS_Nx50),
+ SND_PCI_QUIRK(0x1043, 0x12ff, "ASUS G751", ALC668_FIXUP_ASUS_G751),
+ SND_PCI_QUIRK(0x1043, 0x13df, "Asus N550JX", ALC662_FIXUP_BASS_1A),
+ SND_PCI_QUIRK(0x1043, 0x1477, "ASUS N56VZ", ALC662_FIXUP_BASS_MODE4_CHMAP),
+ SND_PCI_QUIRK(0x1043, 0x15a7, "ASUS UX51VZH", ALC662_FIXUP_BASS_16),
+ SND_PCI_QUIRK(0x1043, 0x177d, "ASUS N551", ALC668_FIXUP_ASUS_Nx51),
+ SND_PCI_QUIRK(0x1043, 0x17bd, "ASUS N751", ALC668_FIXUP_ASUS_Nx51),
+ SND_PCI_QUIRK(0x1043, 0x185d, "ASUS G551JW", ALC668_FIXUP_ASUS_NO_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1963, "ASUS X71SL", ALC662_FIXUP_ASUS_MODE8),
+ SND_PCI_QUIRK(0x1043, 0x1b73, "ASUS N55SF", ALC662_FIXUP_BASS_16),
+ SND_PCI_QUIRK(0x1043, 0x1bf3, "ASUS N76VZ", ALC662_FIXUP_BASS_MODE4_CHMAP),
+ SND_PCI_QUIRK(0x1043, 0x8469, "ASUS mobo", ALC662_FIXUP_NO_JACK_DETECT),
+ SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD),
+ SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE),
+ SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS),
+ SND_PCI_QUIRK(0x17aa, 0x1057, "Lenovo P360", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x1064, "Lenovo P3 Tower", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x32ca, "Lenovo ThinkCentre M80", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x32cb, "Lenovo ThinkCentre M70", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x32cf, "Lenovo ThinkCentre M950", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x32f7, "Lenovo ThinkCentre M90", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x3321, "Lenovo ThinkCentre M70 Gen4", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x331b, "Lenovo ThinkCentre M90 Gen4", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x3364, "Lenovo ThinkCentre M90 Gen5", ALC897_FIXUP_HEADSET_MIC_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x3742, "Lenovo TianYi510Pro-14IOB", ALC897_FIXUP_HEADSET_MIC_PIN2),
+ SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD),
+ SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD),
+ SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO),
+ SND_PCI_QUIRK(0x19da, 0xa130, "Zotac Z68", ALC662_FIXUP_ZOTAC_Z68),
+ SND_PCI_QUIRK(0x1b0a, 0x01b8, "ACER Veriton", ALC662_FIXUP_ACER_VERITON),
+ SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26),
+ SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T),
+ SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB),
+
+#if 0
+ /* Below is a quirk table taken from the old code.
+ * Basically the device should work as is without the fixup table.
+ * If BIOS doesn't give a proper info, enable the corresponding
+ * fixup entry.
+ */
+ SND_PCI_QUIRK(0x1043, 0x1000, "ASUS N50Vm", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1092, "ASUS NB", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x1173, "ASUS K73Jn", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x11c3, "ASUS M70V", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x11d3, "ASUS NB", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x11f3, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1203, "ASUS NB", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1303, "ASUS G60J", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1333, "ASUS G60Jx", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1339, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x13e3, "ASUS N71JA", ALC662_FIXUP_ASUS_MODE7),
+ SND_PCI_QUIRK(0x1043, 0x1463, "ASUS N71", ALC662_FIXUP_ASUS_MODE7),
+ SND_PCI_QUIRK(0x1043, 0x14d3, "ASUS G72", ALC662_FIXUP_ASUS_MODE8),
+ SND_PCI_QUIRK(0x1043, 0x1563, "ASUS N90", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x15d3, "ASUS N50SF F50SF", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x16c3, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x16f3, "ASUS K40C K50C", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1733, "ASUS N81De", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1753, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1763, "ASUS NB", ALC662_FIXUP_ASUS_MODE6),
+ SND_PCI_QUIRK(0x1043, 0x1765, "ASUS NB", ALC662_FIXUP_ASUS_MODE6),
+ SND_PCI_QUIRK(0x1043, 0x1783, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1793, "ASUS F50GX", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x17b3, "ASUS F70SL", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x17f3, "ASUS X58LE", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1813, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1823, "ASUS NB", ALC662_FIXUP_ASUS_MODE5),
+ SND_PCI_QUIRK(0x1043, 0x1833, "ASUS NB", ALC662_FIXUP_ASUS_MODE6),
+ SND_PCI_QUIRK(0x1043, 0x1843, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1853, "ASUS F50Z", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1864, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1876, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1893, "ASUS M50Vm", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x1894, "ASUS X55", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x18b3, "ASUS N80Vc", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x18c3, "ASUS VX5", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS N81Te", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x18f3, "ASUS N505Tp", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1903, "ASUS F5GL", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1913, "ASUS NB", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1933, "ASUS F80Q", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x1943, "ASUS Vx3V", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1953, "ASUS NB", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1963, "ASUS X71C", ALC662_FIXUP_ASUS_MODE3),
+ SND_PCI_QUIRK(0x1043, 0x1983, "ASUS N5051A", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x1993, "ASUS N20", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x19b3, "ASUS F7Z", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x19c3, "ASUS F5Z/F6x", ALC662_FIXUP_ASUS_MODE2),
+ SND_PCI_QUIRK(0x1043, 0x19e3, "ASUS NB", ALC662_FIXUP_ASUS_MODE1),
+ SND_PCI_QUIRK(0x1043, 0x19f3, "ASUS NB", ALC662_FIXUP_ASUS_MODE4),
+#endif
+ {}
+};
+
+static const struct hda_model_fixup alc662_fixup_models[] = {
+ {.id = ALC662_FIXUP_ASPIRE, .name = "aspire"},
+ {.id = ALC662_FIXUP_IDEAPAD, .name = "ideapad"},
+ {.id = ALC272_FIXUP_MARIO, .name = "mario"},
+ {.id = ALC662_FIXUP_HP_RP5800, .name = "hp-rp5800"},
+ {.id = ALC662_FIXUP_ASUS_MODE1, .name = "asus-mode1"},
+ {.id = ALC662_FIXUP_ASUS_MODE2, .name = "asus-mode2"},
+ {.id = ALC662_FIXUP_ASUS_MODE3, .name = "asus-mode3"},
+ {.id = ALC662_FIXUP_ASUS_MODE4, .name = "asus-mode4"},
+ {.id = ALC662_FIXUP_ASUS_MODE5, .name = "asus-mode5"},
+ {.id = ALC662_FIXUP_ASUS_MODE6, .name = "asus-mode6"},
+ {.id = ALC662_FIXUP_ASUS_MODE7, .name = "asus-mode7"},
+ {.id = ALC662_FIXUP_ASUS_MODE8, .name = "asus-mode8"},
+ {.id = ALC662_FIXUP_ZOTAC_Z68, .name = "zotac-z68"},
+ {.id = ALC662_FIXUP_INV_DMIC, .name = "inv-dmic"},
+ {.id = ALC662_FIXUP_DELL_MIC_NO_PRESENCE, .name = "alc662-headset-multi"},
+ {.id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE, .name = "dell-headset-multi"},
+ {.id = ALC662_FIXUP_HEADSET_MODE, .name = "alc662-headset"},
+ {.id = ALC668_FIXUP_HEADSET_MODE, .name = "alc668-headset"},
+ {.id = ALC662_FIXUP_BASS_16, .name = "bass16"},
+ {.id = ALC662_FIXUP_BASS_1A, .name = "bass1a"},
+ {.id = ALC668_FIXUP_AUTO_MUTE, .name = "automute"},
+ {.id = ALC668_FIXUP_DELL_XPS13, .name = "dell-xps13"},
+ {.id = ALC662_FIXUP_ASUS_Nx50, .name = "asus-nx50"},
+ {.id = ALC668_FIXUP_ASUS_Nx51, .name = "asus-nx51"},
+ {.id = ALC668_FIXUP_ASUS_G751, .name = "asus-g751"},
+ {.id = ALC891_FIXUP_HEADSET_MODE, .name = "alc891-headset"},
+ {.id = ALC891_FIXUP_DELL_MIC_NO_PRESENCE, .name = "alc891-headset-multi"},
+ {.id = ALC662_FIXUP_ACER_VERITON, .name = "acer-veriton"},
+ {.id = ALC892_FIXUP_ASROCK_MOBO, .name = "asrock-mobo"},
+ {.id = ALC662_FIXUP_USI_HEADSET_MODE, .name = "usi-headset"},
+ {.id = ALC662_FIXUP_LENOVO_MULTI_CODECS, .name = "dual-codecs"},
+ {.id = ALC669_FIXUP_ACER_ASPIRE_ETHOS, .name = "aspire-ethos"},
+ {.id = ALC897_FIXUP_UNIS_H3C_X500S, .name = "unis-h3c-x500s"},
+ {}
+};
+
+static const struct snd_hda_pin_quirk alc662_pin_fixup_tbl[] = {
+ SND_HDA_PIN_QUIRK(0x10ec0867, 0x1028, "Dell", ALC891_FIXUP_DELL_MIC_NO_PRESENCE,
+ {0x17, 0x02211010},
+ {0x18, 0x01a19030},
+ {0x1a, 0x01813040},
+ {0x21, 0x01014020}),
+ SND_HDA_PIN_QUIRK(0x10ec0867, 0x1028, "Dell", ALC891_FIXUP_DELL_MIC_NO_PRESENCE,
+ {0x16, 0x01813030},
+ {0x17, 0x02211010},
+ {0x18, 0x01a19040},
+ {0x21, 0x01014020}),
+ SND_HDA_PIN_QUIRK(0x10ec0662, 0x1028, "Dell", ALC662_FIXUP_DELL_MIC_NO_PRESENCE,
+ {0x14, 0x01014010},
+ {0x18, 0x01a19020},
+ {0x1a, 0x0181302f},
+ {0x1b, 0x0221401f}),
+ SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE,
+ {0x12, 0x99a30130},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020}),
+ SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE,
+ {0x12, 0x99a30140},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020}),
+ SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE,
+ {0x12, 0x99a30150},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020}),
+ SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE,
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020}),
+ SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell XPS 15", ALC668_FIXUP_AUTO_MUTE,
+ {0x12, 0x90a60130},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f}),
+ SND_HDA_PIN_QUIRK(0x10ec0671, 0x103c, "HP cPC", ALC671_FIXUP_HP_HEADSET_MIC2,
+ {0x14, 0x01014010},
+ {0x17, 0x90170150},
+ {0x19, 0x02a11060},
+ {0x1b, 0x01813030},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0671, 0x103c, "HP cPC", ALC671_FIXUP_HP_HEADSET_MIC2,
+ {0x14, 0x01014010},
+ {0x18, 0x01a19040},
+ {0x1b, 0x01813030},
+ {0x21, 0x02211020}),
+ SND_HDA_PIN_QUIRK(0x10ec0671, 0x103c, "HP cPC", ALC671_FIXUP_HP_HEADSET_MIC2,
+ {0x14, 0x01014020},
+ {0x17, 0x90170110},
+ {0x18, 0x01a19050},
+ {0x1b, 0x01813040},
+ {0x21, 0x02211030}),
+ {}
+};
+
+/*
+ */
+static int patch_alc662(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int err;
+
+ err = alc_alloc_spec(codec, 0x0b);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+
+ spec->shutup = alc_eapd_shutup;
+
+ /* handle multiple HPs as is */
+ spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP;
+
+ alc_fix_pll_init(codec, 0x20, 0x04, 15);
+
+ switch (codec->core.vendor_id) {
+ case 0x10ec0668:
+ spec->init_hook = alc668_restore_default_value;
+ break;
+ }
+
+ alc_pre_init(codec);
+
+ snd_hda_pick_fixup(codec, alc662_fixup_models,
+ alc662_fixup_tbl, alc662_fixups);
+ snd_hda_pick_pin_fixup(codec, alc662_pin_fixup_tbl, alc662_fixups, true);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ alc_auto_parse_customize_define(codec);
+
+ if (has_cdefine_beep(codec))
+ spec->gen.beep_nid = 0x01;
+
+ if ((alc_get_coef0(codec) & (1 << 14)) &&
+ codec->bus->pci && codec->bus->pci->subsystem_vendor == 0x1025 &&
+ spec->cdefine.platform_type == 1) {
+ err = alc_codec_rename(codec, "ALC272X");
+ if (err < 0)
+ goto error;
+ }
+
+ /* automatic parse from the BIOS config */
+ err = alc662_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!spec->gen.no_analog && spec->gen.beep_nid) {
+ switch (codec->core.vendor_id) {
+ case 0x10ec0662:
+ err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+ break;
+ case 0x10ec0272:
+ case 0x10ec0663:
+ case 0x10ec0665:
+ case 0x10ec0668:
+ err = set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT);
+ break;
+ case 0x10ec0273:
+ err = set_beep_amp(spec, 0x0b, 0x03, HDA_INPUT);
+ break;
+ }
+ if (err < 0)
+ goto error;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+
+ error:
+ alc_free(codec);
+ return err;
+}
+
+/*
+ * ALC680 support
+ */
+
+static int alc680_parse_auto_config(struct hda_codec *codec)
+{
+ return alc_parse_auto_config(codec, NULL, NULL);
+}
+
+/*
+ */
+static int patch_alc680(struct hda_codec *codec)
+{
+ int err;
+
+ /* ALC680 has no aa-loopback mixer */
+ err = alc_alloc_spec(codec, 0);
+ if (err < 0)
+ return err;
+
+ /* automatic parse from the BIOS config */
+ err = alc680_parse_auto_config(codec);
+ if (err < 0) {
+ alc_free(codec);
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_realtek[] = {
+ HDA_CODEC_ENTRY(0x10ec0215, "ALC215", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0221, "ALC221", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0222, "ALC222", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0225, "ALC225", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0230, "ALC236", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0231, "ALC231", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0233, "ALC233", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0234, "ALC234", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0235, "ALC233", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0236, "ALC236", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0245, "ALC245", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0255, "ALC255", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0256, "ALC256", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0257, "ALC257", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0260, "ALC260", patch_alc260),
+ HDA_CODEC_ENTRY(0x10ec0262, "ALC262", patch_alc262),
+ HDA_CODEC_ENTRY(0x10ec0267, "ALC267", patch_alc268),
+ HDA_CODEC_ENTRY(0x10ec0268, "ALC268", patch_alc268),
+ HDA_CODEC_ENTRY(0x10ec0269, "ALC269", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0270, "ALC270", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0272, "ALC272", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0274, "ALC274", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0275, "ALC275", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0276, "ALC276", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0280, "ALC280", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0282, "ALC282", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0283, "ALC283", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0284, "ALC284", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0285, "ALC285", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0286, "ALC286", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0287, "ALC287", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0288, "ALC288", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0289, "ALC289", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0290, "ALC290", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0292, "ALC292", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0293, "ALC293", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0294, "ALC294", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0295, "ALC295", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0298, "ALC298", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0299, "ALC299", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0300, "ALC300", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0623, "ALC623", patch_alc269),
+ HDA_CODEC_REV_ENTRY(0x10ec0861, 0x100340, "ALC660", patch_alc861),
+ HDA_CODEC_ENTRY(0x10ec0660, "ALC660-VD", patch_alc861vd),
+ HDA_CODEC_ENTRY(0x10ec0861, "ALC861", patch_alc861),
+ HDA_CODEC_ENTRY(0x10ec0862, "ALC861-VD", patch_alc861vd),
+ HDA_CODEC_REV_ENTRY(0x10ec0662, 0x100002, "ALC662 rev2", patch_alc882),
+ HDA_CODEC_REV_ENTRY(0x10ec0662, 0x100101, "ALC662 rev1", patch_alc662),
+ HDA_CODEC_REV_ENTRY(0x10ec0662, 0x100300, "ALC662 rev3", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0663, "ALC663", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0665, "ALC665", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0667, "ALC667", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0668, "ALC668", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0670, "ALC670", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0671, "ALC671", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0680, "ALC680", patch_alc680),
+ HDA_CODEC_ENTRY(0x10ec0700, "ALC700", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0701, "ALC701", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0703, "ALC703", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0711, "ALC711", patch_alc269),
+ HDA_CODEC_ENTRY(0x10ec0867, "ALC891", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0880, "ALC880", patch_alc880),
+ HDA_CODEC_ENTRY(0x10ec0882, "ALC882", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0883, "ALC883", patch_alc882),
+ HDA_CODEC_REV_ENTRY(0x10ec0885, 0x100101, "ALC889A", patch_alc882),
+ HDA_CODEC_REV_ENTRY(0x10ec0885, 0x100103, "ALC889A", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0885, "ALC885", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0887, "ALC887", patch_alc882),
+ HDA_CODEC_REV_ENTRY(0x10ec0888, 0x100101, "ALC1200", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0888, "ALC888", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0889, "ALC889", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0892, "ALC892", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0897, "ALC897", patch_alc662),
+ HDA_CODEC_ENTRY(0x10ec0899, "ALC898", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0900, "ALC1150", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec0b00, "ALCS1200A", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec1168, "ALC1220", patch_alc882),
+ HDA_CODEC_ENTRY(0x10ec1220, "ALC1220", patch_alc882),
+ HDA_CODEC_ENTRY(0x19e58326, "HW8326", patch_alc269),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_realtek);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Realtek HD-audio codec");
+
+static struct hda_codec_driver realtek_driver = {
+ .id = snd_hda_id_realtek,
+};
+
+module_hda_codec_driver(realtek_driver);
diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c
new file mode 100644
index 000000000..763eae80a
--- /dev/null
+++ b/sound/pci/hda/patch_si3054.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for Silicon Labs 3054/5 modem codec
+ *
+ * Copyright (c) 2005 Sasha Khapyorsky <sashak@alsa-project.org>
+ * Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+
+/* si3054 verbs */
+#define SI3054_VERB_READ_NODE 0x900
+#define SI3054_VERB_WRITE_NODE 0x100
+
+/* si3054 nodes (registers) */
+#define SI3054_EXTENDED_MID 2
+#define SI3054_LINE_RATE 3
+#define SI3054_LINE_LEVEL 4
+#define SI3054_GPIO_CFG 5
+#define SI3054_GPIO_POLARITY 6
+#define SI3054_GPIO_STICKY 7
+#define SI3054_GPIO_WAKEUP 8
+#define SI3054_GPIO_STATUS 9
+#define SI3054_GPIO_CONTROL 10
+#define SI3054_MISC_AFE 11
+#define SI3054_CHIPID 12
+#define SI3054_LINE_CFG1 13
+#define SI3054_LINE_STATUS 14
+#define SI3054_DC_TERMINATION 15
+#define SI3054_LINE_CONFIG 16
+#define SI3054_CALLPROG_ATT 17
+#define SI3054_SQ_CONTROL 18
+#define SI3054_MISC_CONTROL 19
+#define SI3054_RING_CTRL1 20
+#define SI3054_RING_CTRL2 21
+
+/* extended MID */
+#define SI3054_MEI_READY 0xf
+
+/* line level */
+#define SI3054_ATAG_MASK 0x00f0
+#define SI3054_DTAG_MASK 0xf000
+
+/* GPIO bits */
+#define SI3054_GPIO_OH 0x0001
+#define SI3054_GPIO_CID 0x0002
+
+/* chipid and revisions */
+#define SI3054_CHIPID_CODEC_REV_MASK 0x000f
+#define SI3054_CHIPID_DAA_REV_MASK 0x00f0
+#define SI3054_CHIPID_INTERNATIONAL 0x0100
+#define SI3054_CHIPID_DAA_ID 0x0f00
+#define SI3054_CHIPID_CODEC_ID (1<<12)
+
+/* si3054 codec registers (nodes) access macros */
+#define GET_REG(codec,reg) (snd_hda_codec_read(codec,reg,0,SI3054_VERB_READ_NODE,0))
+#define SET_REG(codec,reg,val) (snd_hda_codec_write(codec,reg,0,SI3054_VERB_WRITE_NODE,val))
+#define SET_REG_CACHE(codec,reg,val) \
+ snd_hda_codec_write_cache(codec,reg,0,SI3054_VERB_WRITE_NODE,val)
+
+
+struct si3054_spec {
+ unsigned international;
+};
+
+
+/*
+ * Modem mixer
+ */
+
+#define PRIVATE_VALUE(reg,mask) ((reg<<16)|(mask&0xffff))
+#define PRIVATE_REG(val) ((val>>16)&0xffff)
+#define PRIVATE_MASK(val) (val&0xffff)
+
+#define si3054_switch_info snd_ctl_boolean_mono_info
+
+static int si3054_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 reg = PRIVATE_REG(kcontrol->private_value);
+ u16 mask = PRIVATE_MASK(kcontrol->private_value);
+ uvalue->value.integer.value[0] = (GET_REG(codec, reg)) & mask ? 1 : 0 ;
+ return 0;
+}
+
+static int si3054_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 reg = PRIVATE_REG(kcontrol->private_value);
+ u16 mask = PRIVATE_MASK(kcontrol->private_value);
+ if (uvalue->value.integer.value[0])
+ SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) | mask);
+ else
+ SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) & ~mask);
+ return 0;
+}
+
+#define SI3054_KCONTROL(kname,reg,mask) { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = kname, \
+ .subdevice = HDA_SUBDEV_NID_FLAG | reg, \
+ .info = si3054_switch_info, \
+ .get = si3054_switch_get, \
+ .put = si3054_switch_put, \
+ .private_value = PRIVATE_VALUE(reg,mask), \
+}
+
+
+static const struct snd_kcontrol_new si3054_modem_mixer[] = {
+ SI3054_KCONTROL("Off-hook Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_OH),
+ SI3054_KCONTROL("Caller ID Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_CID),
+ {}
+};
+
+static int si3054_build_controls(struct hda_codec *codec)
+{
+ return snd_hda_add_new_ctls(codec, si3054_modem_mixer);
+}
+
+
+/*
+ * PCM callbacks
+ */
+
+static int si3054_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ u16 val;
+
+ SET_REG(codec, SI3054_LINE_RATE, substream->runtime->rate);
+ val = GET_REG(codec, SI3054_LINE_LEVEL);
+ val &= 0xff << (8 * (substream->stream != SNDRV_PCM_STREAM_PLAYBACK));
+ val |= ((stream_tag & 0xf) << 4) << (8 * (substream->stream == SNDRV_PCM_STREAM_PLAYBACK));
+ SET_REG(codec, SI3054_LINE_LEVEL, val);
+
+ snd_hda_codec_setup_stream(codec, hinfo->nid,
+ stream_tag, 0, format);
+ return 0;
+}
+
+static int si3054_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ static const unsigned int rates[] = { 8000, 9600, 16000 };
+ static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+ };
+ substream->runtime->hw.period_bytes_min = 80;
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+}
+
+
+static const struct hda_pcm_stream si3054_pcm = {
+ .substreams = 1,
+ .channels_min = 1,
+ .channels_max = 1,
+ .nid = 0x1,
+ .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .maxbps = 16,
+ .ops = {
+ .open = si3054_pcm_open,
+ .prepare = si3054_pcm_prepare,
+ },
+};
+
+
+static int si3054_build_pcms(struct hda_codec *codec)
+{
+ struct hda_pcm *info;
+
+ info = snd_hda_codec_pcm_new(codec, "Si3054 Modem");
+ if (!info)
+ return -ENOMEM;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = si3054_pcm;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = codec->core.mfg;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = codec->core.mfg;
+ info->pcm_type = HDA_PCM_TYPE_MODEM;
+ return 0;
+}
+
+
+/*
+ * Init part
+ */
+
+static int si3054_init(struct hda_codec *codec)
+{
+ struct si3054_spec *spec = codec->spec;
+ unsigned wait_count;
+ u16 val;
+
+ if (snd_hdac_regmap_add_vendor_verb(&codec->core,
+ SI3054_VERB_WRITE_NODE))
+ return -ENOMEM;
+
+ snd_hda_codec_write(codec, AC_NODE_ROOT, 0, AC_VERB_SET_CODEC_RESET, 0);
+ snd_hda_codec_write(codec, codec->core.mfg, 0, AC_VERB_SET_STREAM_FORMAT, 0);
+ SET_REG(codec, SI3054_LINE_RATE, 9600);
+ SET_REG(codec, SI3054_LINE_LEVEL, SI3054_DTAG_MASK|SI3054_ATAG_MASK);
+ SET_REG(codec, SI3054_EXTENDED_MID, 0);
+
+ wait_count = 10;
+ do {
+ msleep(2);
+ val = GET_REG(codec, SI3054_EXTENDED_MID);
+ } while ((val & SI3054_MEI_READY) != SI3054_MEI_READY && wait_count--);
+
+ if((val&SI3054_MEI_READY) != SI3054_MEI_READY) {
+ codec_err(codec, "si3054: cannot initialize. EXT MID = %04x\n", val);
+ /* let's pray that this is no fatal error */
+ /* return -EACCES; */
+ }
+
+ SET_REG(codec, SI3054_GPIO_POLARITY, 0xffff);
+ SET_REG(codec, SI3054_GPIO_CFG, 0x0);
+ SET_REG(codec, SI3054_MISC_AFE, 0);
+ SET_REG(codec, SI3054_LINE_CFG1,0x200);
+
+ if((GET_REG(codec,SI3054_LINE_STATUS) & (1<<6)) == 0) {
+ codec_dbg(codec,
+ "Link Frame Detect(FDT) is not ready (line status: %04x)\n",
+ GET_REG(codec,SI3054_LINE_STATUS));
+ }
+
+ spec->international = GET_REG(codec, SI3054_CHIPID) & SI3054_CHIPID_INTERNATIONAL;
+
+ return 0;
+}
+
+static void si3054_free(struct hda_codec *codec)
+{
+ kfree(codec->spec);
+}
+
+
+/*
+ */
+
+static const struct hda_codec_ops si3054_patch_ops = {
+ .build_controls = si3054_build_controls,
+ .build_pcms = si3054_build_pcms,
+ .init = si3054_init,
+ .free = si3054_free,
+};
+
+static int patch_si3054(struct hda_codec *codec)
+{
+ struct si3054_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+ codec->spec = spec;
+ codec->patch_ops = si3054_patch_ops;
+ return 0;
+}
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_si3054[] = {
+ HDA_CODEC_ENTRY(0x163c3055, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x163c3155, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x11c13026, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x11c13055, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x11c13155, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x10573055, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x10573057, "Si3054", patch_si3054),
+ HDA_CODEC_ENTRY(0x10573155, "Si3054", patch_si3054),
+ /* VIA HDA on Clevo m540 */
+ HDA_CODEC_ENTRY(0x11063288, "Si3054", patch_si3054),
+ /* Asus A8J Modem (SM56) */
+ HDA_CODEC_ENTRY(0x15433155, "Si3054", patch_si3054),
+ /* LG LW20 modem */
+ HDA_CODEC_ENTRY(0x18540018, "Si3054", patch_si3054),
+ {}
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_si3054);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Si3054 HD-audio modem codec");
+
+static struct hda_codec_driver si3054_driver = {
+ .id = snd_hda_id_si3054,
+};
+
+module_hda_codec_driver(si3054_driver);
diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c
new file mode 100644
index 000000000..76c5a2b64
--- /dev/null
+++ b/sound/pci/hda/patch_sigmatel.c
@@ -0,0 +1,5174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for SigmaTel STAC92xx
+ *
+ * Copyright (c) 2005 Embedded Alley Solutions, Inc.
+ * Matt Porter <mporter@embeddedalley.com>
+ *
+ * Based on patch_cmedia.c and patch_realtek.c
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_beep.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+enum {
+ STAC_REF,
+ STAC_9200_OQO,
+ STAC_9200_DELL_D21,
+ STAC_9200_DELL_D22,
+ STAC_9200_DELL_D23,
+ STAC_9200_DELL_M21,
+ STAC_9200_DELL_M22,
+ STAC_9200_DELL_M23,
+ STAC_9200_DELL_M24,
+ STAC_9200_DELL_M25,
+ STAC_9200_DELL_M26,
+ STAC_9200_DELL_M27,
+ STAC_9200_M4,
+ STAC_9200_M4_2,
+ STAC_9200_PANASONIC,
+ STAC_9200_EAPD_INIT,
+ STAC_9200_MODELS
+};
+
+enum {
+ STAC_9205_REF,
+ STAC_9205_DELL_M42,
+ STAC_9205_DELL_M43,
+ STAC_9205_DELL_M44,
+ STAC_9205_EAPD,
+ STAC_9205_MODELS
+};
+
+enum {
+ STAC_92HD73XX_NO_JD, /* no jack-detection */
+ STAC_92HD73XX_REF,
+ STAC_92HD73XX_INTEL,
+ STAC_DELL_M6_AMIC,
+ STAC_DELL_M6_DMIC,
+ STAC_DELL_M6_BOTH,
+ STAC_DELL_EQ,
+ STAC_ALIENWARE_M17X,
+ STAC_ELO_VUPOINT_15MX,
+ STAC_92HD89XX_HP_FRONT_JACK,
+ STAC_92HD89XX_HP_Z1_G2_RIGHT_MIC_JACK,
+ STAC_92HD73XX_ASUS_MOBO,
+ STAC_92HD73XX_MODELS
+};
+
+enum {
+ STAC_92HD83XXX_REF,
+ STAC_92HD83XXX_PWR_REF,
+ STAC_DELL_S14,
+ STAC_DELL_VOSTRO_3500,
+ STAC_92HD83XXX_HP_cNB11_INTQUAD,
+ STAC_HP_DV7_4000,
+ STAC_HP_ZEPHYR,
+ STAC_92HD83XXX_HP_LED,
+ STAC_92HD83XXX_HP_INV_LED,
+ STAC_92HD83XXX_HP_MIC_LED,
+ STAC_HP_LED_GPIO10,
+ STAC_92HD83XXX_HEADSET_JACK,
+ STAC_92HD83XXX_HP,
+ STAC_HP_ENVY_BASS,
+ STAC_HP_BNB13_EQ,
+ STAC_HP_ENVY_TS_BASS,
+ STAC_HP_ENVY_TS_DAC_BIND,
+ STAC_92HD83XXX_GPIO10_EAPD,
+ STAC_92HD83XXX_MODELS
+};
+
+enum {
+ STAC_92HD71BXX_REF,
+ STAC_DELL_M4_1,
+ STAC_DELL_M4_2,
+ STAC_DELL_M4_3,
+ STAC_HP_M4,
+ STAC_HP_DV4,
+ STAC_HP_DV5,
+ STAC_HP_HDX,
+ STAC_92HD71BXX_HP,
+ STAC_92HD71BXX_NO_DMIC,
+ STAC_92HD71BXX_NO_SMUX,
+ STAC_92HD71BXX_MODELS
+};
+
+enum {
+ STAC_92HD95_HP_LED,
+ STAC_92HD95_HP_BASS,
+ STAC_92HD95_MODELS
+};
+
+enum {
+ STAC_925x_REF,
+ STAC_M1,
+ STAC_M1_2,
+ STAC_M2,
+ STAC_M2_2,
+ STAC_M3,
+ STAC_M5,
+ STAC_M6,
+ STAC_925x_MODELS
+};
+
+enum {
+ STAC_D945_REF,
+ STAC_D945GTP3,
+ STAC_D945GTP5,
+ STAC_INTEL_MAC_V1,
+ STAC_INTEL_MAC_V2,
+ STAC_INTEL_MAC_V3,
+ STAC_INTEL_MAC_V4,
+ STAC_INTEL_MAC_V5,
+ STAC_INTEL_MAC_AUTO,
+ STAC_ECS_202,
+ STAC_922X_DELL_D81,
+ STAC_922X_DELL_D82,
+ STAC_922X_DELL_M81,
+ STAC_922X_DELL_M82,
+ STAC_922X_INTEL_MAC_GPIO,
+ STAC_922X_MODELS
+};
+
+enum {
+ STAC_D965_REF_NO_JD, /* no jack-detection */
+ STAC_D965_REF,
+ STAC_D965_3ST,
+ STAC_D965_5ST,
+ STAC_D965_5ST_NO_FP,
+ STAC_D965_VERBS,
+ STAC_DELL_3ST,
+ STAC_DELL_BIOS,
+ STAC_NEMO_DEFAULT,
+ STAC_DELL_BIOS_AMIC,
+ STAC_DELL_BIOS_SPDIF,
+ STAC_927X_DELL_DMIC,
+ STAC_927X_VOLKNOB,
+ STAC_927X_MODELS
+};
+
+enum {
+ STAC_9872_VAIO,
+ STAC_9872_MODELS
+};
+
+struct sigmatel_spec {
+ struct hda_gen_spec gen;
+
+ unsigned int eapd_switch: 1;
+ unsigned int linear_tone_beep:1;
+ unsigned int headset_jack:1; /* 4-pin headset jack (hp + mono mic) */
+ unsigned int volknob_init:1; /* special volume-knob initialization */
+ unsigned int powerdown_adcs:1;
+ unsigned int have_spdif_mux:1;
+
+ /* gpio lines */
+ unsigned int eapd_mask;
+ unsigned int gpio_mask;
+ unsigned int gpio_dir;
+ unsigned int gpio_data;
+ unsigned int gpio_mute;
+ unsigned int gpio_led;
+ unsigned int gpio_led_polarity;
+ unsigned int vref_mute_led_nid; /* pin NID for mute-LED vref control */
+ unsigned int vref_led;
+ int default_polarity;
+
+ unsigned int mic_mute_led_gpio; /* capture mute LED GPIO */
+ unsigned int mic_enabled; /* current mic mute state (bitmask) */
+
+ /* stream */
+ unsigned int stream_delay;
+
+ /* analog loopback */
+ const struct snd_kcontrol_new *aloopback_ctl;
+ unsigned int aloopback;
+ unsigned char aloopback_mask;
+ unsigned char aloopback_shift;
+
+ /* power management */
+ unsigned int power_map_bits;
+ unsigned int num_pwrs;
+ const hda_nid_t *pwr_nids;
+ unsigned int active_adcs;
+
+ /* beep widgets */
+ hda_nid_t anabeep_nid;
+ bool beep_power_on;
+
+ /* SPDIF-out mux */
+ const char * const *spdif_labels;
+ struct hda_input_mux spdif_mux;
+ unsigned int cur_smux[2];
+};
+
+#define AC_VERB_IDT_SET_POWER_MAP 0x7ec
+#define AC_VERB_IDT_GET_POWER_MAP 0xfec
+
+static const hda_nid_t stac92hd73xx_pwr_nids[8] = {
+ 0x0a, 0x0b, 0x0c, 0xd, 0x0e,
+ 0x0f, 0x10, 0x11
+};
+
+static const hda_nid_t stac92hd83xxx_pwr_nids[7] = {
+ 0x0a, 0x0b, 0x0c, 0xd, 0x0e,
+ 0x0f, 0x10
+};
+
+static const hda_nid_t stac92hd71bxx_pwr_nids[3] = {
+ 0x0a, 0x0d, 0x0f
+};
+
+
+/*
+ * PCM hooks
+ */
+static void stac_playback_pcm_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ if (action == HDA_GEN_PCM_ACT_OPEN && spec->stream_delay)
+ msleep(spec->stream_delay);
+}
+
+static void stac_capture_pcm_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ int i, idx = 0;
+
+ if (!spec->powerdown_adcs)
+ return;
+
+ for (i = 0; i < spec->gen.num_all_adcs; i++) {
+ if (spec->gen.all_adcs[i] == hinfo->nid) {
+ idx = i;
+ break;
+ }
+ }
+
+ switch (action) {
+ case HDA_GEN_PCM_ACT_OPEN:
+ msleep(40);
+ snd_hda_codec_write(codec, hinfo->nid, 0,
+ AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+ spec->active_adcs |= (1 << idx);
+ break;
+ case HDA_GEN_PCM_ACT_CLOSE:
+ snd_hda_codec_write(codec, hinfo->nid, 0,
+ AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+ spec->active_adcs &= ~(1 << idx);
+ break;
+ }
+}
+
+/*
+ * Early 2006 Intel Macintoshes with STAC9220X5 codecs seem to have a
+ * funky external mute control using GPIO pins.
+ */
+
+static void stac_gpio_set(struct hda_codec *codec, unsigned int mask,
+ unsigned int dir_mask, unsigned int data)
+{
+ unsigned int gpiostate, gpiomask, gpiodir;
+ hda_nid_t fg = codec->core.afg;
+
+ codec_dbg(codec, "%s msk %x dir %x gpio %x\n", __func__, mask, dir_mask, data);
+
+ gpiostate = snd_hda_codec_read(codec, fg, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+ gpiostate = (gpiostate & ~dir_mask) | (data & dir_mask);
+
+ gpiomask = snd_hda_codec_read(codec, fg, 0,
+ AC_VERB_GET_GPIO_MASK, 0);
+ gpiomask |= mask;
+
+ gpiodir = snd_hda_codec_read(codec, fg, 0,
+ AC_VERB_GET_GPIO_DIRECTION, 0);
+ gpiodir |= dir_mask;
+
+ /* Configure GPIOx as CMOS */
+ snd_hda_codec_write(codec, fg, 0, 0x7e7, 0);
+
+ snd_hda_codec_write(codec, fg, 0,
+ AC_VERB_SET_GPIO_MASK, gpiomask);
+ snd_hda_codec_read(codec, fg, 0,
+ AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */
+
+ msleep(1);
+
+ snd_hda_codec_read(codec, fg, 0,
+ AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */
+}
+
+/* hook for controlling mic-mute LED GPIO */
+static int stac_capture_led_update(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (brightness)
+ spec->gpio_data |= spec->mic_mute_led_gpio;
+ else
+ spec->gpio_data &= ~spec->mic_mute_led_gpio;
+ stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
+ return 0;
+}
+
+static int stac_vrefout_set(struct hda_codec *codec,
+ hda_nid_t nid, unsigned int new_vref)
+{
+ int error, pinctl;
+
+ codec_dbg(codec, "%s, nid %x ctl %x\n", __func__, nid, new_vref);
+ pinctl = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+
+ if (pinctl < 0)
+ return pinctl;
+
+ pinctl &= 0xff;
+ pinctl &= ~AC_PINCTL_VREFEN;
+ pinctl |= (new_vref & AC_PINCTL_VREFEN);
+
+ error = snd_hda_set_pin_ctl_cache(codec, nid, pinctl);
+ if (error < 0)
+ return error;
+
+ return 1;
+}
+
+/* prevent codec AFG to D3 state when vref-out pin is used for mute LED */
+/* this hook is set in stac_setup_gpio() */
+static unsigned int stac_vref_led_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ if (nid == codec->core.afg && power_state == AC_PWRST_D3)
+ return AC_PWRST_D1;
+ return snd_hda_gen_path_power_filter(codec, nid, power_state);
+}
+
+/* update mute-LED accoring to the master switch */
+static void stac_update_led_status(struct hda_codec *codec, bool muted)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (!spec->gpio_led)
+ return;
+
+ /* LED state is inverted on these systems */
+ if (spec->gpio_led_polarity)
+ muted = !muted;
+
+ if (!spec->vref_mute_led_nid) {
+ if (muted)
+ spec->gpio_data |= spec->gpio_led;
+ else
+ spec->gpio_data &= ~spec->gpio_led;
+ stac_gpio_set(codec, spec->gpio_mask,
+ spec->gpio_dir, spec->gpio_data);
+ } else {
+ spec->vref_led = muted ? AC_PINCTL_VREF_50 : AC_PINCTL_VREF_GRD;
+ stac_vrefout_set(codec, spec->vref_mute_led_nid,
+ spec->vref_led);
+ }
+}
+
+/* vmaster hook to update mute LED */
+static int stac_vmaster_hook(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
+
+ stac_update_led_status(codec, brightness);
+ return 0;
+}
+
+/* automute hook to handle GPIO mute and EAPD updates */
+static void stac_update_outputs(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (spec->gpio_mute)
+ spec->gen.master_mute =
+ !(snd_hda_codec_read(codec, codec->core.afg, 0,
+ AC_VERB_GET_GPIO_DATA, 0) & spec->gpio_mute);
+
+ snd_hda_gen_update_outputs(codec);
+
+ if (spec->eapd_mask && spec->eapd_switch) {
+ unsigned int val = spec->gpio_data;
+ if (spec->gen.speaker_muted)
+ val &= ~spec->eapd_mask;
+ else
+ val |= spec->eapd_mask;
+ if (spec->gpio_data != val) {
+ spec->gpio_data = val;
+ stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir,
+ val);
+ }
+ }
+}
+
+static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid,
+ bool enable, bool do_write)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ unsigned int idx, val;
+
+ for (idx = 0; idx < spec->num_pwrs; idx++) {
+ if (spec->pwr_nids[idx] == nid)
+ break;
+ }
+ if (idx >= spec->num_pwrs)
+ return;
+
+ idx = 1 << idx;
+
+ val = spec->power_map_bits;
+ if (enable)
+ val &= ~idx;
+ else
+ val |= idx;
+
+ /* power down unused output ports */
+ if (val != spec->power_map_bits) {
+ spec->power_map_bits = val;
+ if (do_write)
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_IDT_SET_POWER_MAP, val);
+ }
+}
+
+/* update power bit per jack plug/unplug */
+static void jack_update_power(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ int i;
+
+ if (!spec->num_pwrs)
+ return;
+
+ if (jack && jack->nid) {
+ stac_toggle_power_map(codec, jack->nid,
+ snd_hda_jack_detect(codec, jack->nid),
+ true);
+ return;
+ }
+
+ /* update all jacks */
+ for (i = 0; i < spec->num_pwrs; i++) {
+ hda_nid_t nid = spec->pwr_nids[i];
+ if (!snd_hda_jack_tbl_get(codec, nid))
+ continue;
+ stac_toggle_power_map(codec, nid,
+ snd_hda_jack_detect(codec, nid),
+ false);
+ }
+
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_IDT_SET_POWER_MAP,
+ spec->power_map_bits);
+}
+
+static void stac_vref_event(struct hda_codec *codec,
+ struct hda_jack_callback *event)
+{
+ unsigned int data;
+
+ data = snd_hda_codec_read(codec, codec->core.afg, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+ /* toggle VREF state based on GPIOx status */
+ snd_hda_codec_write(codec, codec->core.afg, 0, 0x7e0,
+ !!(data & (1 << event->private_data)));
+}
+
+/* initialize the power map and enable the power event to jacks that
+ * haven't been assigned to automute
+ */
+static void stac_init_power_map(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < spec->num_pwrs; i++) {
+ hda_nid_t nid = spec->pwr_nids[i];
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ def_conf = get_defcfg_connect(def_conf);
+ if (def_conf == AC_JACK_PORT_COMPLEX &&
+ spec->vref_mute_led_nid != nid &&
+ is_jack_detectable(codec, nid)) {
+ snd_hda_jack_detect_enable_callback(codec, nid,
+ jack_update_power);
+ } else {
+ if (def_conf == AC_JACK_PORT_NONE)
+ stac_toggle_power_map(codec, nid, false, false);
+ else
+ stac_toggle_power_map(codec, nid, true, false);
+ }
+ }
+}
+
+/*
+ */
+
+static inline bool get_int_hint(struct hda_codec *codec, const char *key,
+ int *valp)
+{
+ return !snd_hda_get_int_hint(codec, key, valp);
+}
+
+/* override some hints from the hwdep entry */
+static void stac_store_hints(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ int val;
+
+ if (get_int_hint(codec, "gpio_mask", &spec->gpio_mask)) {
+ spec->eapd_mask = spec->gpio_dir = spec->gpio_data =
+ spec->gpio_mask;
+ }
+ if (get_int_hint(codec, "gpio_dir", &spec->gpio_dir))
+ spec->gpio_dir &= spec->gpio_mask;
+ if (get_int_hint(codec, "gpio_data", &spec->gpio_data))
+ spec->gpio_data &= spec->gpio_mask;
+ if (get_int_hint(codec, "eapd_mask", &spec->eapd_mask))
+ spec->eapd_mask &= spec->gpio_mask;
+ if (get_int_hint(codec, "gpio_mute", &spec->gpio_mute))
+ spec->gpio_mute &= spec->gpio_mask;
+ val = snd_hda_get_bool_hint(codec, "eapd_switch");
+ if (val >= 0)
+ spec->eapd_switch = val;
+}
+
+/*
+ * loopback controls
+ */
+
+#define stac_aloopback_info snd_ctl_boolean_mono_info
+
+static int stac_aloopback_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ struct sigmatel_spec *spec = codec->spec;
+
+ ucontrol->value.integer.value[0] = !!(spec->aloopback &
+ (spec->aloopback_mask << idx));
+ return 0;
+}
+
+static int stac_aloopback_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct sigmatel_spec *spec = codec->spec;
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned int dac_mode;
+ unsigned int val, idx_val;
+
+ idx_val = spec->aloopback_mask << idx;
+ if (ucontrol->value.integer.value[0])
+ val = spec->aloopback | idx_val;
+ else
+ val = spec->aloopback & ~idx_val;
+ if (spec->aloopback == val)
+ return 0;
+
+ spec->aloopback = val;
+
+ /* Only return the bits defined by the shift value of the
+ * first two bytes of the mask
+ */
+ dac_mode = snd_hda_codec_read(codec, codec->core.afg, 0,
+ kcontrol->private_value & 0xFFFF, 0x0);
+ dac_mode >>= spec->aloopback_shift;
+
+ if (spec->aloopback & idx_val) {
+ snd_hda_power_up(codec);
+ dac_mode |= idx_val;
+ } else {
+ snd_hda_power_down(codec);
+ dac_mode &= ~idx_val;
+ }
+
+ snd_hda_codec_write_cache(codec, codec->core.afg, 0,
+ kcontrol->private_value >> 16, dac_mode);
+
+ return 1;
+}
+
+#define STAC_ANALOG_LOOPBACK(verb_read, verb_write, cnt) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = "Analog Loopback", \
+ .count = cnt, \
+ .info = stac_aloopback_info, \
+ .get = stac_aloopback_get, \
+ .put = stac_aloopback_put, \
+ .private_value = verb_read | (verb_write << 16), \
+ }
+
+/*
+ * Mute LED handling on HP laptops
+ */
+
+/* check whether it's a HP laptop with a docking port */
+static bool hp_bnb2011_with_dock(struct hda_codec *codec)
+{
+ if (codec->core.vendor_id != 0x111d7605 &&
+ codec->core.vendor_id != 0x111d76d1)
+ return false;
+
+ switch (codec->core.subsystem_id) {
+ case 0x103c1618:
+ case 0x103c1619:
+ case 0x103c161a:
+ case 0x103c161b:
+ case 0x103c161c:
+ case 0x103c161d:
+ case 0x103c161e:
+ case 0x103c161f:
+
+ case 0x103c162a:
+ case 0x103c162b:
+
+ case 0x103c1630:
+ case 0x103c1631:
+
+ case 0x103c1633:
+ case 0x103c1634:
+ case 0x103c1635:
+
+ case 0x103c3587:
+ case 0x103c3588:
+ case 0x103c3589:
+ case 0x103c358a:
+
+ case 0x103c3667:
+ case 0x103c3668:
+ case 0x103c3669:
+
+ return true;
+ }
+ return false;
+}
+
+static bool hp_blike_system(u32 subsystem_id)
+{
+ switch (subsystem_id) {
+ case 0x103c1473: /* HP ProBook 6550b */
+ case 0x103c1520:
+ case 0x103c1521:
+ case 0x103c1523:
+ case 0x103c1524:
+ case 0x103c1525:
+ case 0x103c1722:
+ case 0x103c1723:
+ case 0x103c1724:
+ case 0x103c1725:
+ case 0x103c1726:
+ case 0x103c1727:
+ case 0x103c1728:
+ case 0x103c1729:
+ case 0x103c172a:
+ case 0x103c172b:
+ case 0x103c307e:
+ case 0x103c307f:
+ case 0x103c3080:
+ case 0x103c3081:
+ case 0x103c7007:
+ case 0x103c7008:
+ return true;
+ }
+ return false;
+}
+
+static void set_hp_led_gpio(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ unsigned int gpio;
+
+ if (spec->gpio_led)
+ return;
+
+ gpio = snd_hda_param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP);
+ gpio &= AC_GPIO_IO_COUNT;
+ if (gpio > 3)
+ spec->gpio_led = 0x08; /* GPIO 3 */
+ else
+ spec->gpio_led = 0x01; /* GPIO 0 */
+}
+
+/*
+ * This method searches for the mute LED GPIO configuration
+ * provided as OEM string in SMBIOS. The format of that string
+ * is HP_Mute_LED_P_G or HP_Mute_LED_P
+ * where P can be 0 or 1 and defines mute LED GPIO control state (low/high)
+ * that corresponds to the NOT muted state of the master volume
+ * and G is the index of the GPIO to use as the mute LED control (0..9)
+ * If _G portion is missing it is assigned based on the codec ID
+ *
+ * So, HP B-series like systems may have HP_Mute_LED_0 (current models)
+ * or HP_Mute_LED_0_3 (future models) OEM SMBIOS strings
+ *
+ *
+ * The dv-series laptops don't seem to have the HP_Mute_LED* strings in
+ * SMBIOS - at least the ones I have seen do not have them - which include
+ * my own system (HP Pavilion dv6-1110ax) and my cousin's
+ * HP Pavilion dv9500t CTO.
+ * Need more information on whether it is true across the entire series.
+ * -- kunal
+ */
+static int find_mute_led_cfg(struct hda_codec *codec, int default_polarity)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ const struct dmi_device *dev = NULL;
+
+ if (get_int_hint(codec, "gpio_led", &spec->gpio_led)) {
+ get_int_hint(codec, "gpio_led_polarity",
+ &spec->gpio_led_polarity);
+ return 1;
+ }
+
+ while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+ if (sscanf(dev->name, "HP_Mute_LED_%u_%x",
+ &spec->gpio_led_polarity,
+ &spec->gpio_led) == 2) {
+ unsigned int max_gpio;
+ max_gpio = snd_hda_param_read(codec, codec->core.afg,
+ AC_PAR_GPIO_CAP);
+ max_gpio &= AC_GPIO_IO_COUNT;
+ if (spec->gpio_led < max_gpio)
+ spec->gpio_led = 1 << spec->gpio_led;
+ else
+ spec->vref_mute_led_nid = spec->gpio_led;
+ return 1;
+ }
+ if (sscanf(dev->name, "HP_Mute_LED_%u",
+ &spec->gpio_led_polarity) == 1) {
+ set_hp_led_gpio(codec);
+ return 1;
+ }
+ /* BIOS bug: unfilled OEM string */
+ if (strstr(dev->name, "HP_Mute_LED_P_G")) {
+ set_hp_led_gpio(codec);
+ if (default_polarity >= 0)
+ spec->gpio_led_polarity = default_polarity;
+ else
+ spec->gpio_led_polarity = 1;
+ return 1;
+ }
+ }
+
+ /*
+ * Fallback case - if we don't find the DMI strings,
+ * we statically set the GPIO - if not a B-series system
+ * and default polarity is provided
+ */
+ if (!hp_blike_system(codec->core.subsystem_id) &&
+ (default_polarity == 0 || default_polarity == 1)) {
+ set_hp_led_gpio(codec);
+ spec->gpio_led_polarity = default_polarity;
+ return 1;
+ }
+ return 0;
+}
+
+/* check whether a built-in speaker is included in parsed pins */
+static bool has_builtin_speaker(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ const hda_nid_t *nid_pin;
+ int nids, i;
+
+ if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) {
+ nid_pin = spec->gen.autocfg.line_out_pins;
+ nids = spec->gen.autocfg.line_outs;
+ } else {
+ nid_pin = spec->gen.autocfg.speaker_pins;
+ nids = spec->gen.autocfg.speaker_outs;
+ }
+
+ for (i = 0; i < nids; i++) {
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid_pin[i]);
+ if (snd_hda_get_input_pin_attr(def_conf) == INPUT_PIN_ATTR_INT)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * PC beep controls
+ */
+
+/* create PC beep volume controls */
+static int stac_auto_create_beep_ctls(struct hda_codec *codec,
+ hda_nid_t nid)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ u32 caps = query_amp_caps(codec, nid, HDA_OUTPUT);
+ struct snd_kcontrol_new *knew;
+ static const struct snd_kcontrol_new abeep_mute_ctl =
+ HDA_CODEC_MUTE(NULL, 0, 0, 0);
+ static const struct snd_kcontrol_new dbeep_mute_ctl =
+ HDA_CODEC_MUTE_BEEP(NULL, 0, 0, 0);
+ static const struct snd_kcontrol_new beep_vol_ctl =
+ HDA_CODEC_VOLUME(NULL, 0, 0, 0);
+
+ /* check for mute support for the amp */
+ if ((caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT) {
+ const struct snd_kcontrol_new *temp;
+ if (spec->anabeep_nid == nid)
+ temp = &abeep_mute_ctl;
+ else
+ temp = &dbeep_mute_ctl;
+ knew = snd_hda_gen_add_kctl(&spec->gen,
+ "Beep Playback Switch", temp);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value =
+ HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT);
+ }
+
+ /* check to see if there is volume support for the amp */
+ if ((caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT) {
+ knew = snd_hda_gen_add_kctl(&spec->gen,
+ "Beep Playback Volume",
+ &beep_vol_ctl);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value =
+ HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+#define stac_dig_beep_switch_info snd_ctl_boolean_mono_info
+
+static int stac_dig_beep_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = codec->beep->enabled;
+ return 0;
+}
+
+static int stac_dig_beep_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ return snd_hda_enable_beep_device(codec, ucontrol->value.integer.value[0]);
+}
+
+static const struct snd_kcontrol_new stac_dig_beep_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Beep Playback Switch",
+ .info = stac_dig_beep_switch_info,
+ .get = stac_dig_beep_switch_get,
+ .put = stac_dig_beep_switch_put,
+};
+
+static int stac_beep_switch_ctl(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &stac_dig_beep_ctrl))
+ return -ENOMEM;
+ return 0;
+}
+#endif
+
+/*
+ * SPDIF-out mux controls
+ */
+
+static int stac_smux_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct sigmatel_spec *spec = codec->spec;
+ return snd_hda_input_mux_info(&spec->spdif_mux, uinfo);
+}
+
+static int stac_smux_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct sigmatel_spec *spec = codec->spec;
+ unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ ucontrol->value.enumerated.item[0] = spec->cur_smux[smux_idx];
+ return 0;
+}
+
+static int stac_smux_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct sigmatel_spec *spec = codec->spec;
+ unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ return snd_hda_input_mux_put(codec, &spec->spdif_mux, ucontrol,
+ spec->gen.autocfg.dig_out_pins[smux_idx],
+ &spec->cur_smux[smux_idx]);
+}
+
+static const struct snd_kcontrol_new stac_smux_mixer = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "IEC958 Playback Source",
+ /* count set later */
+ .info = stac_smux_enum_info,
+ .get = stac_smux_enum_get,
+ .put = stac_smux_enum_put,
+};
+
+static const char * const stac_spdif_labels[] = {
+ "Digital Playback", "Analog Mux 1", "Analog Mux 2", NULL
+};
+
+static int stac_create_spdif_mux_ctls(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ const char * const *labels = spec->spdif_labels;
+ struct snd_kcontrol_new *kctl;
+ int i, num_cons;
+
+ if (cfg->dig_outs < 1)
+ return 0;
+
+ num_cons = snd_hda_get_num_conns(codec, cfg->dig_out_pins[0]);
+ if (num_cons <= 1)
+ return 0;
+
+ if (!labels)
+ labels = stac_spdif_labels;
+ for (i = 0; i < num_cons; i++) {
+ if (snd_BUG_ON(!labels[i]))
+ return -EINVAL;
+ snd_hda_add_imux_item(codec, &spec->spdif_mux, labels[i], i, NULL);
+ }
+
+ kctl = snd_hda_gen_add_kctl(&spec->gen, NULL, &stac_smux_mixer);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->count = cfg->dig_outs;
+
+ return 0;
+}
+
+static const struct hda_verb stac9200_eapd_init[] = {
+ /* set dac0mux for dac converter */
+ {0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+ {0x08, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+ {}
+};
+
+static const struct hda_verb dell_eq_core_init[] = {
+ /* set master volume to max value without distortion
+ * and direct control */
+ { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xec},
+ {}
+};
+
+static const struct hda_verb stac92hd73xx_core_init[] = {
+ /* set master volume and direct control */
+ { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+ {}
+};
+
+static const struct hda_verb stac92hd83xxx_core_init[] = {
+ /* power state controls amps */
+ { 0x01, AC_VERB_SET_EAPD, 1 << 2},
+ {}
+};
+
+static const struct hda_verb stac92hd83xxx_hp_zephyr_init[] = {
+ { 0x22, 0x785, 0x43 },
+ { 0x22, 0x782, 0xe0 },
+ { 0x22, 0x795, 0x00 },
+ {}
+};
+
+static const struct hda_verb stac92hd71bxx_core_init[] = {
+ /* set master volume and direct control */
+ { 0x28, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+ {}
+};
+
+static const hda_nid_t stac92hd71bxx_unmute_nids[] = {
+ /* unmute right and left channels for nodes 0x0f, 0xa, 0x0d */
+ 0x0f, 0x0a, 0x0d, 0
+};
+
+static const struct hda_verb stac925x_core_init[] = {
+ /* set dac0mux for dac converter */
+ { 0x06, AC_VERB_SET_CONNECT_SEL, 0x00},
+ /* mute the master volume */
+ { 0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+ {}
+};
+
+static const struct hda_verb stac922x_core_init[] = {
+ /* set master volume and direct control */
+ { 0x16, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+ {}
+};
+
+static const struct hda_verb d965_core_init[] = {
+ /* unmute node 0x1b */
+ { 0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* select node 0x03 as DAC */
+ { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x01},
+ {}
+};
+
+static const struct hda_verb dell_3st_core_init[] = {
+ /* don't set delta bit */
+ {0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0x7f},
+ /* unmute node 0x1b */
+ {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* select node 0x03 as DAC */
+ {0x0b, AC_VERB_SET_CONNECT_SEL, 0x01},
+ {}
+};
+
+static const struct hda_verb stac927x_core_init[] = {
+ /* set master volume and direct control */
+ { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+ /* enable analog pc beep path */
+ { 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5},
+ {}
+};
+
+static const struct hda_verb stac927x_volknob_core_init[] = {
+ /* don't set delta bit */
+ {0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0x7f},
+ /* enable analog pc beep path */
+ {0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5},
+ {}
+};
+
+static const struct hda_verb stac9205_core_init[] = {
+ /* set master volume and direct control */
+ { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+ /* enable analog pc beep path */
+ { 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5},
+ {}
+};
+
+static const struct snd_kcontrol_new stac92hd73xx_6ch_loopback =
+ STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 3);
+
+static const struct snd_kcontrol_new stac92hd73xx_8ch_loopback =
+ STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 4);
+
+static const struct snd_kcontrol_new stac92hd73xx_10ch_loopback =
+ STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 5);
+
+static const struct snd_kcontrol_new stac92hd71bxx_loopback =
+ STAC_ANALOG_LOOPBACK(0xFA0, 0x7A0, 2);
+
+static const struct snd_kcontrol_new stac9205_loopback =
+ STAC_ANALOG_LOOPBACK(0xFE0, 0x7E0, 1);
+
+static const struct snd_kcontrol_new stac927x_loopback =
+ STAC_ANALOG_LOOPBACK(0xFEB, 0x7EB, 1);
+
+static const struct hda_pintbl ref9200_pin_configs[] = {
+ { 0x08, 0x01c47010 },
+ { 0x09, 0x01447010 },
+ { 0x0d, 0x0221401f },
+ { 0x0e, 0x01114010 },
+ { 0x0f, 0x02a19020 },
+ { 0x10, 0x01a19021 },
+ { 0x11, 0x90100140 },
+ { 0x12, 0x01813122 },
+ {}
+};
+
+static const struct hda_pintbl gateway9200_m4_pin_configs[] = {
+ { 0x08, 0x400000fe },
+ { 0x09, 0x404500f4 },
+ { 0x0d, 0x400100f0 },
+ { 0x0e, 0x90110010 },
+ { 0x0f, 0x400100f1 },
+ { 0x10, 0x02a1902e },
+ { 0x11, 0x500000f2 },
+ { 0x12, 0x500000f3 },
+ {}
+};
+
+static const struct hda_pintbl gateway9200_m4_2_pin_configs[] = {
+ { 0x08, 0x400000fe },
+ { 0x09, 0x404500f4 },
+ { 0x0d, 0x400100f0 },
+ { 0x0e, 0x90110010 },
+ { 0x0f, 0x400100f1 },
+ { 0x10, 0x02a1902e },
+ { 0x11, 0x500000f2 },
+ { 0x12, 0x500000f3 },
+ {}
+};
+
+/*
+ STAC 9200 pin configs for
+ 102801A8
+ 102801DE
+ 102801E8
+*/
+static const struct hda_pintbl dell9200_d21_pin_configs[] = {
+ { 0x08, 0x400001f0 },
+ { 0x09, 0x400001f1 },
+ { 0x0d, 0x02214030 },
+ { 0x0e, 0x01014010 },
+ { 0x0f, 0x02a19020 },
+ { 0x10, 0x01a19021 },
+ { 0x11, 0x90100140 },
+ { 0x12, 0x01813122 },
+ {}
+};
+
+/*
+ STAC 9200 pin configs for
+ 102801C0
+ 102801C1
+*/
+static const struct hda_pintbl dell9200_d22_pin_configs[] = {
+ { 0x08, 0x400001f0 },
+ { 0x09, 0x400001f1 },
+ { 0x0d, 0x0221401f },
+ { 0x0e, 0x01014010 },
+ { 0x0f, 0x01813020 },
+ { 0x10, 0x02a19021 },
+ { 0x11, 0x90100140 },
+ { 0x12, 0x400001f2 },
+ {}
+};
+
+/*
+ STAC 9200 pin configs for
+ 102801C4 (Dell Dimension E310)
+ 102801C5
+ 102801C7
+ 102801D9
+ 102801DA
+ 102801E3
+*/
+static const struct hda_pintbl dell9200_d23_pin_configs[] = {
+ { 0x08, 0x400001f0 },
+ { 0x09, 0x400001f1 },
+ { 0x0d, 0x0221401f },
+ { 0x0e, 0x01014010 },
+ { 0x0f, 0x01813020 },
+ { 0x10, 0x01a19021 },
+ { 0x11, 0x90100140 },
+ { 0x12, 0x400001f2 },
+ {}
+};
+
+
+/*
+ STAC 9200-32 pin configs for
+ 102801B5 (Dell Inspiron 630m)
+ 102801D8 (Dell Inspiron 640m)
+*/
+static const struct hda_pintbl dell9200_m21_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x03441340 },
+ { 0x0d, 0x0321121f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x408003fb },
+ { 0x10, 0x03a11020 },
+ { 0x11, 0x401003fc },
+ { 0x12, 0x403003fd },
+ {}
+};
+
+/*
+ STAC 9200-32 pin configs for
+ 102801C2 (Dell Latitude D620)
+ 102801C8
+ 102801CC (Dell Latitude D820)
+ 102801D4
+ 102801D6
+*/
+static const struct hda_pintbl dell9200_m22_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x0144131f },
+ { 0x0d, 0x0321121f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x90a70321 },
+ { 0x10, 0x03a11020 },
+ { 0x11, 0x401003fb },
+ { 0x12, 0x40f000fc },
+ {}
+};
+
+/*
+ STAC 9200-32 pin configs for
+ 102801CE (Dell XPS M1710)
+ 102801CF (Dell Precision M90)
+*/
+static const struct hda_pintbl dell9200_m23_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x01441340 },
+ { 0x0d, 0x0421421f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x408003fb },
+ { 0x10, 0x04a1102e },
+ { 0x11, 0x90170311 },
+ { 0x12, 0x403003fc },
+ {}
+};
+
+/*
+ STAC 9200-32 pin configs for
+ 102801C9
+ 102801CA
+ 102801CB (Dell Latitude 120L)
+ 102801D3
+*/
+static const struct hda_pintbl dell9200_m24_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x404003fb },
+ { 0x0d, 0x0321121f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x408003fc },
+ { 0x10, 0x03a11020 },
+ { 0x11, 0x401003fd },
+ { 0x12, 0x403003fe },
+ {}
+};
+
+/*
+ STAC 9200-32 pin configs for
+ 102801BD (Dell Inspiron E1505n)
+ 102801EE
+ 102801EF
+*/
+static const struct hda_pintbl dell9200_m25_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x01441340 },
+ { 0x0d, 0x0421121f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x408003fb },
+ { 0x10, 0x04a11020 },
+ { 0x11, 0x401003fc },
+ { 0x12, 0x403003fd },
+ {}
+};
+
+/*
+ STAC 9200-32 pin configs for
+ 102801F5 (Dell Inspiron 1501)
+ 102801F6
+*/
+static const struct hda_pintbl dell9200_m26_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x404003fb },
+ { 0x0d, 0x0421121f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x408003fc },
+ { 0x10, 0x04a11020 },
+ { 0x11, 0x401003fd },
+ { 0x12, 0x403003fe },
+ {}
+};
+
+/*
+ STAC 9200-32
+ 102801CD (Dell Inspiron E1705/9400)
+*/
+static const struct hda_pintbl dell9200_m27_pin_configs[] = {
+ { 0x08, 0x40c003fa },
+ { 0x09, 0x01441340 },
+ { 0x0d, 0x0421121f },
+ { 0x0e, 0x90170310 },
+ { 0x0f, 0x90170310 },
+ { 0x10, 0x04a11020 },
+ { 0x11, 0x90170310 },
+ { 0x12, 0x40f003fc },
+ {}
+};
+
+static const struct hda_pintbl oqo9200_pin_configs[] = {
+ { 0x08, 0x40c000f0 },
+ { 0x09, 0x404000f1 },
+ { 0x0d, 0x0221121f },
+ { 0x0e, 0x02211210 },
+ { 0x0f, 0x90170111 },
+ { 0x10, 0x90a70120 },
+ { 0x11, 0x400000f2 },
+ { 0x12, 0x400000f3 },
+ {}
+};
+
+/*
+ * STAC 92HD700
+ * 18881000 Amigaone X1000
+ */
+static const struct hda_pintbl nemo_pin_configs[] = {
+ { 0x0a, 0x02214020 }, /* Front panel HP socket */
+ { 0x0b, 0x02a19080 }, /* Front Mic */
+ { 0x0c, 0x0181304e }, /* Line in */
+ { 0x0d, 0x01014010 }, /* Line out */
+ { 0x0e, 0x01a19040 }, /* Rear Mic */
+ { 0x0f, 0x01011012 }, /* Rear speakers */
+ { 0x10, 0x01016011 }, /* Center speaker */
+ { 0x11, 0x01012014 }, /* Side speakers (7.1) */
+ { 0x12, 0x103301f0 }, /* Motherboard CD line in connector */
+ { 0x13, 0x411111f0 }, /* Unused */
+ { 0x14, 0x411111f0 }, /* Unused */
+ { 0x21, 0x01442170 }, /* S/PDIF line out */
+ { 0x22, 0x411111f0 }, /* Unused */
+ { 0x23, 0x411111f0 }, /* Unused */
+ {}
+};
+
+static void stac9200_fixup_panasonic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gpio_mask = spec->gpio_dir = 0x09;
+ spec->gpio_data = 0x00;
+ /* CF-74 has no headphone detection, and the driver should *NOT*
+ * do detection and HP/speaker toggle because the hardware does it.
+ */
+ spec->gen.suppress_auto_mute = 1;
+ }
+}
+
+
+static const struct hda_fixup stac9200_fixups[] = {
+ [STAC_REF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = ref9200_pin_configs,
+ },
+ [STAC_9200_OQO] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = oqo9200_pin_configs,
+ .chained = true,
+ .chain_id = STAC_9200_EAPD_INIT,
+ },
+ [STAC_9200_DELL_D21] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_d21_pin_configs,
+ },
+ [STAC_9200_DELL_D22] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_d22_pin_configs,
+ },
+ [STAC_9200_DELL_D23] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_d23_pin_configs,
+ },
+ [STAC_9200_DELL_M21] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m21_pin_configs,
+ },
+ [STAC_9200_DELL_M22] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m22_pin_configs,
+ },
+ [STAC_9200_DELL_M23] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m23_pin_configs,
+ },
+ [STAC_9200_DELL_M24] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m24_pin_configs,
+ },
+ [STAC_9200_DELL_M25] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m25_pin_configs,
+ },
+ [STAC_9200_DELL_M26] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m26_pin_configs,
+ },
+ [STAC_9200_DELL_M27] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell9200_m27_pin_configs,
+ },
+ [STAC_9200_M4] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = gateway9200_m4_pin_configs,
+ .chained = true,
+ .chain_id = STAC_9200_EAPD_INIT,
+ },
+ [STAC_9200_M4_2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = gateway9200_m4_2_pin_configs,
+ .chained = true,
+ .chain_id = STAC_9200_EAPD_INIT,
+ },
+ [STAC_9200_PANASONIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac9200_fixup_panasonic,
+ },
+ [STAC_9200_EAPD_INIT] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x08, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+ {}
+ },
+ },
+};
+
+static const struct hda_model_fixup stac9200_models[] = {
+ { .id = STAC_REF, .name = "ref" },
+ { .id = STAC_9200_OQO, .name = "oqo" },
+ { .id = STAC_9200_DELL_D21, .name = "dell-d21" },
+ { .id = STAC_9200_DELL_D22, .name = "dell-d22" },
+ { .id = STAC_9200_DELL_D23, .name = "dell-d23" },
+ { .id = STAC_9200_DELL_M21, .name = "dell-m21" },
+ { .id = STAC_9200_DELL_M22, .name = "dell-m22" },
+ { .id = STAC_9200_DELL_M23, .name = "dell-m23" },
+ { .id = STAC_9200_DELL_M24, .name = "dell-m24" },
+ { .id = STAC_9200_DELL_M25, .name = "dell-m25" },
+ { .id = STAC_9200_DELL_M26, .name = "dell-m26" },
+ { .id = STAC_9200_DELL_M27, .name = "dell-m27" },
+ { .id = STAC_9200_M4, .name = "gateway-m4" },
+ { .id = STAC_9200_M4_2, .name = "gateway-m4-2" },
+ { .id = STAC_9200_PANASONIC, .name = "panasonic" },
+ {}
+};
+
+static const struct snd_pci_quirk stac9200_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_REF),
+ /* Dell laptops have BIOS problem */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a8,
+ "unknown Dell", STAC_9200_DELL_D21),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01b5,
+ "Dell Inspiron 630m", STAC_9200_DELL_M21),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bd,
+ "Dell Inspiron E1505n", STAC_9200_DELL_M25),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c0,
+ "unknown Dell", STAC_9200_DELL_D22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c1,
+ "unknown Dell", STAC_9200_DELL_D22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c2,
+ "Dell Latitude D620", STAC_9200_DELL_M22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c5,
+ "unknown Dell", STAC_9200_DELL_D23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c7,
+ "unknown Dell", STAC_9200_DELL_D23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c8,
+ "unknown Dell", STAC_9200_DELL_M22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c9,
+ "unknown Dell", STAC_9200_DELL_M24),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ca,
+ "unknown Dell", STAC_9200_DELL_M24),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cb,
+ "Dell Latitude 120L", STAC_9200_DELL_M24),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cc,
+ "Dell Latitude D820", STAC_9200_DELL_M22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cd,
+ "Dell Inspiron E1705/9400", STAC_9200_DELL_M27),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ce,
+ "Dell XPS M1710", STAC_9200_DELL_M23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cf,
+ "Dell Precision M90", STAC_9200_DELL_M23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d3,
+ "unknown Dell", STAC_9200_DELL_M22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d4,
+ "unknown Dell", STAC_9200_DELL_M22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d6,
+ "unknown Dell", STAC_9200_DELL_M22),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d8,
+ "Dell Inspiron 640m", STAC_9200_DELL_M21),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d9,
+ "unknown Dell", STAC_9200_DELL_D23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01da,
+ "unknown Dell", STAC_9200_DELL_D23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01de,
+ "unknown Dell", STAC_9200_DELL_D21),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e3,
+ "unknown Dell", STAC_9200_DELL_D23),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e8,
+ "unknown Dell", STAC_9200_DELL_D21),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ee,
+ "unknown Dell", STAC_9200_DELL_M25),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ef,
+ "unknown Dell", STAC_9200_DELL_M25),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f5,
+ "Dell Inspiron 1501", STAC_9200_DELL_M26),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f6,
+ "unknown Dell", STAC_9200_DELL_M26),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0201,
+ "Dell Latitude D430", STAC_9200_DELL_M22),
+ /* Panasonic */
+ SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-74", STAC_9200_PANASONIC),
+ /* Gateway machines needs EAPD to be set on resume */
+ SND_PCI_QUIRK(0x107b, 0x0205, "Gateway S-7110M", STAC_9200_M4),
+ SND_PCI_QUIRK(0x107b, 0x0317, "Gateway MT3423, MX341*", STAC_9200_M4_2),
+ SND_PCI_QUIRK(0x107b, 0x0318, "Gateway ML3019, MT3707", STAC_9200_M4_2),
+ /* OQO Mobile */
+ SND_PCI_QUIRK(0x1106, 0x3288, "OQO Model 2", STAC_9200_OQO),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl ref925x_pin_configs[] = {
+ { 0x07, 0x40c003f0 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x01813022 },
+ { 0x0b, 0x02a19021 },
+ { 0x0c, 0x90a70320 },
+ { 0x0d, 0x02214210 },
+ { 0x10, 0x01019020 },
+ { 0x11, 0x9033032e },
+ {}
+};
+
+static const struct hda_pintbl stac925xM1_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x9033032e },
+ {}
+};
+
+static const struct hda_pintbl stac925xM1_2_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x9033032e },
+ {}
+};
+
+static const struct hda_pintbl stac925xM2_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x9033032e },
+ {}
+};
+
+static const struct hda_pintbl stac925xM2_2_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x9033032e },
+ {}
+};
+
+static const struct hda_pintbl stac925xM3_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x503303f3 },
+ {}
+};
+
+static const struct hda_pintbl stac925xM5_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x9033032e },
+ {}
+};
+
+static const struct hda_pintbl stac925xM6_pin_configs[] = {
+ { 0x07, 0x40c003f4 },
+ { 0x08, 0x424503f2 },
+ { 0x0a, 0x400000f3 },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x40a000f0 },
+ { 0x0d, 0x90100210 },
+ { 0x10, 0x400003f1 },
+ { 0x11, 0x90330320 },
+ {}
+};
+
+static const struct hda_fixup stac925x_fixups[] = {
+ [STAC_REF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = ref925x_pin_configs,
+ },
+ [STAC_M1] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM1_pin_configs,
+ },
+ [STAC_M1_2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM1_2_pin_configs,
+ },
+ [STAC_M2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM2_pin_configs,
+ },
+ [STAC_M2_2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM2_2_pin_configs,
+ },
+ [STAC_M3] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM3_pin_configs,
+ },
+ [STAC_M5] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM5_pin_configs,
+ },
+ [STAC_M6] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac925xM6_pin_configs,
+ },
+};
+
+static const struct hda_model_fixup stac925x_models[] = {
+ { .id = STAC_REF, .name = "ref" },
+ { .id = STAC_M1, .name = "m1" },
+ { .id = STAC_M1_2, .name = "m1-2" },
+ { .id = STAC_M2, .name = "m2" },
+ { .id = STAC_M2_2, .name = "m2-2" },
+ { .id = STAC_M3, .name = "m3" },
+ { .id = STAC_M5, .name = "m5" },
+ { .id = STAC_M6, .name = "m6" },
+ {}
+};
+
+static const struct snd_pci_quirk stac925x_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, "DFI LanParty", STAC_REF),
+ SND_PCI_QUIRK(0x8384, 0x7632, "Stac9202 Reference Board", STAC_REF),
+
+ /* Default table for unknown ID */
+ SND_PCI_QUIRK(0x1002, 0x437b, "Gateway mobile", STAC_M2_2),
+
+ /* gateway machines are checked via codec ssid */
+ SND_PCI_QUIRK(0x107b, 0x0316, "Gateway M255", STAC_M2),
+ SND_PCI_QUIRK(0x107b, 0x0366, "Gateway MP6954", STAC_M5),
+ SND_PCI_QUIRK(0x107b, 0x0461, "Gateway NX560XL", STAC_M1),
+ SND_PCI_QUIRK(0x107b, 0x0681, "Gateway NX860", STAC_M2),
+ SND_PCI_QUIRK(0x107b, 0x0367, "Gateway MX6453", STAC_M1_2),
+ /* Not sure about the brand name for those */
+ SND_PCI_QUIRK(0x107b, 0x0281, "Gateway mobile", STAC_M1),
+ SND_PCI_QUIRK(0x107b, 0x0507, "Gateway mobile", STAC_M3),
+ SND_PCI_QUIRK(0x107b, 0x0281, "Gateway mobile", STAC_M6),
+ SND_PCI_QUIRK(0x107b, 0x0685, "Gateway mobile", STAC_M2_2),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl ref92hd73xx_pin_configs[] = {
+ // Port A-H
+ { 0x0a, 0x02214030 },
+ { 0x0b, 0x02a19040 },
+ { 0x0c, 0x01a19020 },
+ { 0x0d, 0x02214030 },
+ { 0x0e, 0x0181302e },
+ { 0x0f, 0x01014010 },
+ { 0x10, 0x01014020 },
+ { 0x11, 0x01014030 },
+ // CD in
+ { 0x12, 0x02319040 },
+ // Digial Mic ins
+ { 0x13, 0x90a000f0 },
+ { 0x14, 0x90a000f0 },
+ // Digital outs
+ { 0x22, 0x01452050 },
+ { 0x23, 0x01452050 },
+ {}
+};
+
+static const struct hda_pintbl dell_m6_pin_configs[] = {
+ { 0x0a, 0x0321101f },
+ { 0x0b, 0x4f00000f },
+ { 0x0c, 0x4f0000f0 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x03a11020 },
+ { 0x0f, 0x0321101f },
+ { 0x10, 0x4f0000f0 },
+ { 0x11, 0x4f0000f0 },
+ { 0x12, 0x4f0000f0 },
+ { 0x13, 0x90a60160 },
+ { 0x14, 0x4f0000f0 },
+ { 0x22, 0x4f0000f0 },
+ { 0x23, 0x4f0000f0 },
+ {}
+};
+
+static const struct hda_pintbl alienware_m17x_pin_configs[] = {
+ { 0x0a, 0x0321101f },
+ { 0x0b, 0x0321101f },
+ { 0x0c, 0x03a11020 },
+ { 0x0d, 0x03014020 },
+ { 0x0e, 0x90170110 },
+ { 0x0f, 0x4f0000f0 },
+ { 0x10, 0x4f0000f0 },
+ { 0x11, 0x4f0000f0 },
+ { 0x12, 0x4f0000f0 },
+ { 0x13, 0x90a60160 },
+ { 0x14, 0x4f0000f0 },
+ { 0x22, 0x4f0000f0 },
+ { 0x23, 0x904601b0 },
+ {}
+};
+
+static const struct hda_pintbl intel_dg45id_pin_configs[] = {
+ // Analog outputs
+ { 0x0a, 0x02214230 },
+ { 0x0b, 0x02A19240 },
+ { 0x0c, 0x01013214 },
+ { 0x0d, 0x01014210 },
+ { 0x0e, 0x01A19250 },
+ { 0x0f, 0x01011212 },
+ { 0x10, 0x01016211 },
+ // Digital output
+ { 0x22, 0x01451380 },
+ { 0x23, 0x40f000f0 },
+ {}
+};
+
+static const struct hda_pintbl stac92hd89xx_hp_front_jack_pin_configs[] = {
+ { 0x0a, 0x02214030 },
+ { 0x0b, 0x02A19010 },
+ {}
+};
+
+static const struct hda_pintbl stac92hd89xx_hp_z1_g2_right_mic_jack_pin_configs[] = {
+ { 0x0e, 0x400000f0 },
+ {}
+};
+
+static void stac92hd73xx_fixup_ref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ snd_hda_apply_pincfgs(codec, ref92hd73xx_pin_configs);
+ spec->gpio_mask = spec->gpio_dir = spec->gpio_data = 0;
+}
+
+static void stac92hd73xx_fixup_dell(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ snd_hda_apply_pincfgs(codec, dell_m6_pin_configs);
+ spec->eapd_switch = 0;
+}
+
+static void stac92hd73xx_fixup_dell_eq(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ stac92hd73xx_fixup_dell(codec);
+ snd_hda_add_verbs(codec, dell_eq_core_init);
+ spec->volknob_init = 1;
+}
+
+/* Analog Mics */
+static void stac92hd73xx_fixup_dell_m6_amic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ stac92hd73xx_fixup_dell(codec);
+ snd_hda_codec_set_pincfg(codec, 0x0b, 0x90A70170);
+}
+
+/* Digital Mics */
+static void stac92hd73xx_fixup_dell_m6_dmic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ stac92hd73xx_fixup_dell(codec);
+ snd_hda_codec_set_pincfg(codec, 0x13, 0x90A60160);
+}
+
+/* Both */
+static void stac92hd73xx_fixup_dell_m6_both(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ stac92hd73xx_fixup_dell(codec);
+ snd_hda_codec_set_pincfg(codec, 0x0b, 0x90A70170);
+ snd_hda_codec_set_pincfg(codec, 0x13, 0x90A60160);
+}
+
+static void stac92hd73xx_fixup_alienware_m17x(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ snd_hda_apply_pincfgs(codec, alienware_m17x_pin_configs);
+ spec->eapd_switch = 0;
+}
+
+static void stac92hd73xx_fixup_no_jd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ codec->no_jack_detect = 1;
+}
+
+
+static void stac92hd73xx_disable_automute(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ spec->gen.suppress_auto_mute = 1;
+}
+
+static const struct hda_fixup stac92hd73xx_fixups[] = {
+ [STAC_92HD73XX_REF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_ref,
+ },
+ [STAC_DELL_M6_AMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_dell_m6_amic,
+ },
+ [STAC_DELL_M6_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_dell_m6_dmic,
+ },
+ [STAC_DELL_M6_BOTH] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_dell_m6_both,
+ },
+ [STAC_DELL_EQ] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_dell_eq,
+ },
+ [STAC_ALIENWARE_M17X] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_alienware_m17x,
+ },
+ [STAC_ELO_VUPOINT_15MX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_disable_automute,
+ },
+ [STAC_92HD73XX_INTEL] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = intel_dg45id_pin_configs,
+ },
+ [STAC_92HD73XX_NO_JD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd73xx_fixup_no_jd,
+ },
+ [STAC_92HD89XX_HP_FRONT_JACK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac92hd89xx_hp_front_jack_pin_configs,
+ },
+ [STAC_92HD89XX_HP_Z1_G2_RIGHT_MIC_JACK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac92hd89xx_hp_z1_g2_right_mic_jack_pin_configs,
+ },
+ [STAC_92HD73XX_ASUS_MOBO] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* enable 5.1 and SPDIF out */
+ { 0x0c, 0x01014411 },
+ { 0x0d, 0x01014410 },
+ { 0x0e, 0x01014412 },
+ { 0x22, 0x014b1180 },
+ { }
+ }
+ },
+};
+
+static const struct hda_model_fixup stac92hd73xx_models[] = {
+ { .id = STAC_92HD73XX_NO_JD, .name = "no-jd" },
+ { .id = STAC_92HD73XX_REF, .name = "ref" },
+ { .id = STAC_92HD73XX_INTEL, .name = "intel" },
+ { .id = STAC_DELL_M6_AMIC, .name = "dell-m6-amic" },
+ { .id = STAC_DELL_M6_DMIC, .name = "dell-m6-dmic" },
+ { .id = STAC_DELL_M6_BOTH, .name = "dell-m6" },
+ { .id = STAC_DELL_EQ, .name = "dell-eq" },
+ { .id = STAC_ALIENWARE_M17X, .name = "alienware" },
+ { .id = STAC_ELO_VUPOINT_15MX, .name = "elo-vupoint-15mx" },
+ { .id = STAC_92HD73XX_ASUS_MOBO, .name = "asus-mobo" },
+ {}
+};
+
+static const struct snd_pci_quirk stac92hd73xx_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_92HD73XX_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_92HD73XX_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5001,
+ "Intel DP45SG", STAC_92HD73XX_INTEL),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5002,
+ "Intel DG45ID", STAC_92HD73XX_INTEL),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5003,
+ "Intel DG45FC", STAC_92HD73XX_INTEL),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0254,
+ "Dell Studio 1535", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0255,
+ "unknown Dell", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0256,
+ "unknown Dell", STAC_DELL_M6_BOTH),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0257,
+ "unknown Dell", STAC_DELL_M6_BOTH),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025e,
+ "unknown Dell", STAC_DELL_M6_AMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025f,
+ "unknown Dell", STAC_DELL_M6_AMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0271,
+ "unknown Dell", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0272,
+ "unknown Dell", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x029f,
+ "Dell Studio 1537", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a0,
+ "Dell Studio 17", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02be,
+ "Dell Studio 1555", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02bd,
+ "Dell Studio 1557", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02fe,
+ "Dell Studio XPS 1645", STAC_DELL_M6_DMIC),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0413,
+ "Dell Studio 1558", STAC_DELL_M6_DMIC),
+ /* codec SSID matching */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a1,
+ "Alienware M17x", STAC_ALIENWARE_M17X),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x043a,
+ "Alienware M17x", STAC_ALIENWARE_M17X),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0490,
+ "Alienware M17x R3", STAC_DELL_EQ),
+ SND_PCI_QUIRK(0x1059, 0x1011,
+ "ELO VuPoint 15MX", STAC_ELO_VUPOINT_15MX),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1927,
+ "HP Z1 G2", STAC_92HD89XX_HP_Z1_G2_RIGHT_MIC_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2b17,
+ "unknown HP", STAC_92HD89XX_HP_FRONT_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_ASUSTEK, 0x83f8, "ASUS AT4NM10",
+ STAC_92HD73XX_ASUS_MOBO),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl ref92hd83xxx_pin_configs[] = {
+ { 0x0a, 0x02214030 },
+ { 0x0b, 0x02211010 },
+ { 0x0c, 0x02a19020 },
+ { 0x0d, 0x02170130 },
+ { 0x0e, 0x01014050 },
+ { 0x0f, 0x01819040 },
+ { 0x10, 0x01014020 },
+ { 0x11, 0x90a3014e },
+ { 0x1f, 0x01451160 },
+ { 0x20, 0x98560170 },
+ {}
+};
+
+static const struct hda_pintbl dell_s14_pin_configs[] = {
+ { 0x0a, 0x0221403f },
+ { 0x0b, 0x0221101f },
+ { 0x0c, 0x02a19020 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x40f000f0 },
+ { 0x0f, 0x40f000f0 },
+ { 0x10, 0x40f000f0 },
+ { 0x11, 0x90a60160 },
+ { 0x1f, 0x40f000f0 },
+ { 0x20, 0x40f000f0 },
+ {}
+};
+
+static const struct hda_pintbl dell_vostro_3500_pin_configs[] = {
+ { 0x0a, 0x02a11020 },
+ { 0x0b, 0x0221101f },
+ { 0x0c, 0x400000f0 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x400000f1 },
+ { 0x0f, 0x400000f2 },
+ { 0x10, 0x400000f3 },
+ { 0x11, 0x90a60160 },
+ { 0x1f, 0x400000f4 },
+ { 0x20, 0x400000f5 },
+ {}
+};
+
+static const struct hda_pintbl hp_dv7_4000_pin_configs[] = {
+ { 0x0a, 0x03a12050 },
+ { 0x0b, 0x0321201f },
+ { 0x0c, 0x40f000f0 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x40f000f0 },
+ { 0x0f, 0x40f000f0 },
+ { 0x10, 0x90170110 },
+ { 0x11, 0xd5a30140 },
+ { 0x1f, 0x40f000f0 },
+ { 0x20, 0x40f000f0 },
+ {}
+};
+
+static const struct hda_pintbl hp_zephyr_pin_configs[] = {
+ { 0x0a, 0x01813050 },
+ { 0x0b, 0x0421201f },
+ { 0x0c, 0x04a1205e },
+ { 0x0d, 0x96130310 },
+ { 0x0e, 0x96130310 },
+ { 0x0f, 0x0101401f },
+ { 0x10, 0x1111611f },
+ { 0x11, 0xd5a30130 },
+ {}
+};
+
+static const struct hda_pintbl hp_cNB11_intquad_pin_configs[] = {
+ { 0x0a, 0x40f000f0 },
+ { 0x0b, 0x0221101f },
+ { 0x0c, 0x02a11020 },
+ { 0x0d, 0x92170110 },
+ { 0x0e, 0x40f000f0 },
+ { 0x0f, 0x92170110 },
+ { 0x10, 0x40f000f0 },
+ { 0x11, 0xd5a30130 },
+ { 0x1f, 0x40f000f0 },
+ { 0x20, 0x40f000f0 },
+ {}
+};
+
+static void stac92hd83xxx_fixup_hp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ if (hp_bnb2011_with_dock(codec)) {
+ snd_hda_codec_set_pincfg(codec, 0xa, 0x2101201f);
+ snd_hda_codec_set_pincfg(codec, 0xf, 0x2181205e);
+ }
+
+ if (find_mute_led_cfg(codec, spec->default_polarity))
+ codec_dbg(codec, "mute LED gpio %d polarity %d\n",
+ spec->gpio_led,
+ spec->gpio_led_polarity);
+
+ /* allow auto-switching of dock line-in */
+ spec->gen.line_in_auto_switch = true;
+}
+
+static void stac92hd83xxx_fixup_hp_zephyr(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ snd_hda_apply_pincfgs(codec, hp_zephyr_pin_configs);
+ snd_hda_add_verbs(codec, stac92hd83xxx_hp_zephyr_init);
+}
+
+static void stac92hd83xxx_fixup_hp_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->default_polarity = 0;
+}
+
+static void stac92hd83xxx_fixup_hp_inv_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->default_polarity = 1;
+}
+
+static void stac92hd83xxx_fixup_hp_mic_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->mic_mute_led_gpio = 0x08; /* GPIO3 */
+#ifdef CONFIG_PM
+ /* resetting controller clears GPIO, so we need to keep on */
+ codec->core.power_caps &= ~AC_PWRST_CLKSTOP;
+#endif
+ }
+}
+
+static void stac92hd83xxx_fixup_hp_led_gpio10(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gpio_led = 0x10; /* GPIO4 */
+ spec->default_polarity = 0;
+ }
+}
+
+static void stac92hd83xxx_fixup_headset_jack(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->headset_jack = 1;
+}
+
+static void stac92hd83xxx_fixup_gpio10_eapd(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ spec->eapd_mask = spec->gpio_mask = spec->gpio_dir =
+ spec->gpio_data = 0x10;
+ spec->eapd_switch = 0;
+}
+
+static void hp_envy_ts_fixup_dac_bind(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ static const hda_nid_t preferred_pairs[] = {
+ 0xd, 0x13,
+ 0
+ };
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ spec->gen.preferred_dacs = preferred_pairs;
+}
+
+static const struct hda_verb hp_bnb13_eq_verbs[] = {
+ /* 44.1KHz base */
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0x68 },
+ { 0x22, 0x7A8, 0x17 },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0x68 },
+ { 0x22, 0x7AB, 0x17 },
+ { 0x22, 0x7AC, 0x00 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x83 },
+ { 0x22, 0x7A7, 0x2F },
+ { 0x22, 0x7A8, 0xD1 },
+ { 0x22, 0x7A9, 0x83 },
+ { 0x22, 0x7AA, 0x2F },
+ { 0x22, 0x7AB, 0xD1 },
+ { 0x22, 0x7AC, 0x01 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0x68 },
+ { 0x22, 0x7A8, 0x17 },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0x68 },
+ { 0x22, 0x7AB, 0x17 },
+ { 0x22, 0x7AC, 0x02 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x7C },
+ { 0x22, 0x7A7, 0xC6 },
+ { 0x22, 0x7A8, 0x0C },
+ { 0x22, 0x7A9, 0x7C },
+ { 0x22, 0x7AA, 0xC6 },
+ { 0x22, 0x7AB, 0x0C },
+ { 0x22, 0x7AC, 0x03 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xC3 },
+ { 0x22, 0x7A7, 0x25 },
+ { 0x22, 0x7A8, 0xAF },
+ { 0x22, 0x7A9, 0xC3 },
+ { 0x22, 0x7AA, 0x25 },
+ { 0x22, 0x7AB, 0xAF },
+ { 0x22, 0x7AC, 0x04 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0x85 },
+ { 0x22, 0x7A8, 0x73 },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0x85 },
+ { 0x22, 0x7AB, 0x73 },
+ { 0x22, 0x7AC, 0x05 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x85 },
+ { 0x22, 0x7A7, 0x39 },
+ { 0x22, 0x7A8, 0xC7 },
+ { 0x22, 0x7A9, 0x85 },
+ { 0x22, 0x7AA, 0x39 },
+ { 0x22, 0x7AB, 0xC7 },
+ { 0x22, 0x7AC, 0x06 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3C },
+ { 0x22, 0x7A7, 0x90 },
+ { 0x22, 0x7A8, 0xB0 },
+ { 0x22, 0x7A9, 0x3C },
+ { 0x22, 0x7AA, 0x90 },
+ { 0x22, 0x7AB, 0xB0 },
+ { 0x22, 0x7AC, 0x07 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x7A },
+ { 0x22, 0x7A7, 0xC6 },
+ { 0x22, 0x7A8, 0x39 },
+ { 0x22, 0x7A9, 0x7A },
+ { 0x22, 0x7AA, 0xC6 },
+ { 0x22, 0x7AB, 0x39 },
+ { 0x22, 0x7AC, 0x08 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xC4 },
+ { 0x22, 0x7A7, 0xE9 },
+ { 0x22, 0x7A8, 0xDC },
+ { 0x22, 0x7A9, 0xC4 },
+ { 0x22, 0x7AA, 0xE9 },
+ { 0x22, 0x7AB, 0xDC },
+ { 0x22, 0x7AC, 0x09 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3D },
+ { 0x22, 0x7A7, 0xE1 },
+ { 0x22, 0x7A8, 0x0D },
+ { 0x22, 0x7A9, 0x3D },
+ { 0x22, 0x7AA, 0xE1 },
+ { 0x22, 0x7AB, 0x0D },
+ { 0x22, 0x7AC, 0x0A },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x89 },
+ { 0x22, 0x7A7, 0xB6 },
+ { 0x22, 0x7A8, 0xEB },
+ { 0x22, 0x7A9, 0x89 },
+ { 0x22, 0x7AA, 0xB6 },
+ { 0x22, 0x7AB, 0xEB },
+ { 0x22, 0x7AC, 0x0B },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x39 },
+ { 0x22, 0x7A7, 0x9D },
+ { 0x22, 0x7A8, 0xFE },
+ { 0x22, 0x7A9, 0x39 },
+ { 0x22, 0x7AA, 0x9D },
+ { 0x22, 0x7AB, 0xFE },
+ { 0x22, 0x7AC, 0x0C },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x76 },
+ { 0x22, 0x7A7, 0x49 },
+ { 0x22, 0x7A8, 0x15 },
+ { 0x22, 0x7A9, 0x76 },
+ { 0x22, 0x7AA, 0x49 },
+ { 0x22, 0x7AB, 0x15 },
+ { 0x22, 0x7AC, 0x0D },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xC8 },
+ { 0x22, 0x7A7, 0x80 },
+ { 0x22, 0x7A8, 0xF5 },
+ { 0x22, 0x7A9, 0xC8 },
+ { 0x22, 0x7AA, 0x80 },
+ { 0x22, 0x7AB, 0xF5 },
+ { 0x22, 0x7AC, 0x0E },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x40 },
+ { 0x22, 0x7A7, 0x00 },
+ { 0x22, 0x7A8, 0x00 },
+ { 0x22, 0x7A9, 0x40 },
+ { 0x22, 0x7AA, 0x00 },
+ { 0x22, 0x7AB, 0x00 },
+ { 0x22, 0x7AC, 0x0F },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x90 },
+ { 0x22, 0x7A7, 0x68 },
+ { 0x22, 0x7A8, 0xF1 },
+ { 0x22, 0x7A9, 0x90 },
+ { 0x22, 0x7AA, 0x68 },
+ { 0x22, 0x7AB, 0xF1 },
+ { 0x22, 0x7AC, 0x10 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x34 },
+ { 0x22, 0x7A7, 0x47 },
+ { 0x22, 0x7A8, 0x6C },
+ { 0x22, 0x7A9, 0x34 },
+ { 0x22, 0x7AA, 0x47 },
+ { 0x22, 0x7AB, 0x6C },
+ { 0x22, 0x7AC, 0x11 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x6F },
+ { 0x22, 0x7A7, 0x97 },
+ { 0x22, 0x7A8, 0x0F },
+ { 0x22, 0x7A9, 0x6F },
+ { 0x22, 0x7AA, 0x97 },
+ { 0x22, 0x7AB, 0x0F },
+ { 0x22, 0x7AC, 0x12 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xCB },
+ { 0x22, 0x7A7, 0xB8 },
+ { 0x22, 0x7A8, 0x94 },
+ { 0x22, 0x7A9, 0xCB },
+ { 0x22, 0x7AA, 0xB8 },
+ { 0x22, 0x7AB, 0x94 },
+ { 0x22, 0x7AC, 0x13 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x40 },
+ { 0x22, 0x7A7, 0x00 },
+ { 0x22, 0x7A8, 0x00 },
+ { 0x22, 0x7A9, 0x40 },
+ { 0x22, 0x7AA, 0x00 },
+ { 0x22, 0x7AB, 0x00 },
+ { 0x22, 0x7AC, 0x14 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x95 },
+ { 0x22, 0x7A7, 0x76 },
+ { 0x22, 0x7A8, 0x5B },
+ { 0x22, 0x7A9, 0x95 },
+ { 0x22, 0x7AA, 0x76 },
+ { 0x22, 0x7AB, 0x5B },
+ { 0x22, 0x7AC, 0x15 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x31 },
+ { 0x22, 0x7A7, 0xAC },
+ { 0x22, 0x7A8, 0x31 },
+ { 0x22, 0x7A9, 0x31 },
+ { 0x22, 0x7AA, 0xAC },
+ { 0x22, 0x7AB, 0x31 },
+ { 0x22, 0x7AC, 0x16 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x6A },
+ { 0x22, 0x7A7, 0x89 },
+ { 0x22, 0x7A8, 0xA5 },
+ { 0x22, 0x7A9, 0x6A },
+ { 0x22, 0x7AA, 0x89 },
+ { 0x22, 0x7AB, 0xA5 },
+ { 0x22, 0x7AC, 0x17 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xCE },
+ { 0x22, 0x7A7, 0x53 },
+ { 0x22, 0x7A8, 0xCF },
+ { 0x22, 0x7A9, 0xCE },
+ { 0x22, 0x7AA, 0x53 },
+ { 0x22, 0x7AB, 0xCF },
+ { 0x22, 0x7AC, 0x18 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x40 },
+ { 0x22, 0x7A7, 0x00 },
+ { 0x22, 0x7A8, 0x00 },
+ { 0x22, 0x7A9, 0x40 },
+ { 0x22, 0x7AA, 0x00 },
+ { 0x22, 0x7AB, 0x00 },
+ { 0x22, 0x7AC, 0x19 },
+ { 0x22, 0x7AD, 0x80 },
+ /* 48KHz base */
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0x88 },
+ { 0x22, 0x7A8, 0xDC },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0x88 },
+ { 0x22, 0x7AB, 0xDC },
+ { 0x22, 0x7AC, 0x1A },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x82 },
+ { 0x22, 0x7A7, 0xEE },
+ { 0x22, 0x7A8, 0x46 },
+ { 0x22, 0x7A9, 0x82 },
+ { 0x22, 0x7AA, 0xEE },
+ { 0x22, 0x7AB, 0x46 },
+ { 0x22, 0x7AC, 0x1B },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0x88 },
+ { 0x22, 0x7A8, 0xDC },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0x88 },
+ { 0x22, 0x7AB, 0xDC },
+ { 0x22, 0x7AC, 0x1C },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x7D },
+ { 0x22, 0x7A7, 0x09 },
+ { 0x22, 0x7A8, 0x28 },
+ { 0x22, 0x7A9, 0x7D },
+ { 0x22, 0x7AA, 0x09 },
+ { 0x22, 0x7AB, 0x28 },
+ { 0x22, 0x7AC, 0x1D },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xC2 },
+ { 0x22, 0x7A7, 0xE5 },
+ { 0x22, 0x7A8, 0xB4 },
+ { 0x22, 0x7A9, 0xC2 },
+ { 0x22, 0x7AA, 0xE5 },
+ { 0x22, 0x7AB, 0xB4 },
+ { 0x22, 0x7AC, 0x1E },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0xA3 },
+ { 0x22, 0x7A8, 0x1F },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0xA3 },
+ { 0x22, 0x7AB, 0x1F },
+ { 0x22, 0x7AC, 0x1F },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x84 },
+ { 0x22, 0x7A7, 0xCA },
+ { 0x22, 0x7A8, 0xF1 },
+ { 0x22, 0x7A9, 0x84 },
+ { 0x22, 0x7AA, 0xCA },
+ { 0x22, 0x7AB, 0xF1 },
+ { 0x22, 0x7AC, 0x20 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3C },
+ { 0x22, 0x7A7, 0xD5 },
+ { 0x22, 0x7A8, 0x9C },
+ { 0x22, 0x7A9, 0x3C },
+ { 0x22, 0x7AA, 0xD5 },
+ { 0x22, 0x7AB, 0x9C },
+ { 0x22, 0x7AC, 0x21 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x7B },
+ { 0x22, 0x7A7, 0x35 },
+ { 0x22, 0x7A8, 0x0F },
+ { 0x22, 0x7A9, 0x7B },
+ { 0x22, 0x7AA, 0x35 },
+ { 0x22, 0x7AB, 0x0F },
+ { 0x22, 0x7AC, 0x22 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xC4 },
+ { 0x22, 0x7A7, 0x87 },
+ { 0x22, 0x7A8, 0x45 },
+ { 0x22, 0x7A9, 0xC4 },
+ { 0x22, 0x7AA, 0x87 },
+ { 0x22, 0x7AB, 0x45 },
+ { 0x22, 0x7AC, 0x23 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3E },
+ { 0x22, 0x7A7, 0x0A },
+ { 0x22, 0x7A8, 0x78 },
+ { 0x22, 0x7A9, 0x3E },
+ { 0x22, 0x7AA, 0x0A },
+ { 0x22, 0x7AB, 0x78 },
+ { 0x22, 0x7AC, 0x24 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x88 },
+ { 0x22, 0x7A7, 0xE2 },
+ { 0x22, 0x7A8, 0x05 },
+ { 0x22, 0x7A9, 0x88 },
+ { 0x22, 0x7AA, 0xE2 },
+ { 0x22, 0x7AB, 0x05 },
+ { 0x22, 0x7AC, 0x25 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x3A },
+ { 0x22, 0x7A7, 0x1A },
+ { 0x22, 0x7A8, 0xA3 },
+ { 0x22, 0x7A9, 0x3A },
+ { 0x22, 0x7AA, 0x1A },
+ { 0x22, 0x7AB, 0xA3 },
+ { 0x22, 0x7AC, 0x26 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x77 },
+ { 0x22, 0x7A7, 0x1D },
+ { 0x22, 0x7A8, 0xFB },
+ { 0x22, 0x7A9, 0x77 },
+ { 0x22, 0x7AA, 0x1D },
+ { 0x22, 0x7AB, 0xFB },
+ { 0x22, 0x7AC, 0x27 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xC7 },
+ { 0x22, 0x7A7, 0xDA },
+ { 0x22, 0x7A8, 0xE5 },
+ { 0x22, 0x7A9, 0xC7 },
+ { 0x22, 0x7AA, 0xDA },
+ { 0x22, 0x7AB, 0xE5 },
+ { 0x22, 0x7AC, 0x28 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x40 },
+ { 0x22, 0x7A7, 0x00 },
+ { 0x22, 0x7A8, 0x00 },
+ { 0x22, 0x7A9, 0x40 },
+ { 0x22, 0x7AA, 0x00 },
+ { 0x22, 0x7AB, 0x00 },
+ { 0x22, 0x7AC, 0x29 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x8E },
+ { 0x22, 0x7A7, 0xD7 },
+ { 0x22, 0x7A8, 0x22 },
+ { 0x22, 0x7A9, 0x8E },
+ { 0x22, 0x7AA, 0xD7 },
+ { 0x22, 0x7AB, 0x22 },
+ { 0x22, 0x7AC, 0x2A },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x35 },
+ { 0x22, 0x7A7, 0x26 },
+ { 0x22, 0x7A8, 0xC6 },
+ { 0x22, 0x7A9, 0x35 },
+ { 0x22, 0x7AA, 0x26 },
+ { 0x22, 0x7AB, 0xC6 },
+ { 0x22, 0x7AC, 0x2B },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x71 },
+ { 0x22, 0x7A7, 0x28 },
+ { 0x22, 0x7A8, 0xDE },
+ { 0x22, 0x7A9, 0x71 },
+ { 0x22, 0x7AA, 0x28 },
+ { 0x22, 0x7AB, 0xDE },
+ { 0x22, 0x7AC, 0x2C },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xCA },
+ { 0x22, 0x7A7, 0xD9 },
+ { 0x22, 0x7A8, 0x3A },
+ { 0x22, 0x7A9, 0xCA },
+ { 0x22, 0x7AA, 0xD9 },
+ { 0x22, 0x7AB, 0x3A },
+ { 0x22, 0x7AC, 0x2D },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x40 },
+ { 0x22, 0x7A7, 0x00 },
+ { 0x22, 0x7A8, 0x00 },
+ { 0x22, 0x7A9, 0x40 },
+ { 0x22, 0x7AA, 0x00 },
+ { 0x22, 0x7AB, 0x00 },
+ { 0x22, 0x7AC, 0x2E },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x93 },
+ { 0x22, 0x7A7, 0x5E },
+ { 0x22, 0x7A8, 0xD8 },
+ { 0x22, 0x7A9, 0x93 },
+ { 0x22, 0x7AA, 0x5E },
+ { 0x22, 0x7AB, 0xD8 },
+ { 0x22, 0x7AC, 0x2F },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x32 },
+ { 0x22, 0x7A7, 0xB7 },
+ { 0x22, 0x7A8, 0xB1 },
+ { 0x22, 0x7A9, 0x32 },
+ { 0x22, 0x7AA, 0xB7 },
+ { 0x22, 0x7AB, 0xB1 },
+ { 0x22, 0x7AC, 0x30 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x6C },
+ { 0x22, 0x7A7, 0xA1 },
+ { 0x22, 0x7A8, 0x28 },
+ { 0x22, 0x7A9, 0x6C },
+ { 0x22, 0x7AA, 0xA1 },
+ { 0x22, 0x7AB, 0x28 },
+ { 0x22, 0x7AC, 0x31 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0xCD },
+ { 0x22, 0x7A7, 0x48 },
+ { 0x22, 0x7A8, 0x4F },
+ { 0x22, 0x7A9, 0xCD },
+ { 0x22, 0x7AA, 0x48 },
+ { 0x22, 0x7AB, 0x4F },
+ { 0x22, 0x7AC, 0x32 },
+ { 0x22, 0x7AD, 0x80 },
+ { 0x22, 0x7A6, 0x40 },
+ { 0x22, 0x7A7, 0x00 },
+ { 0x22, 0x7A8, 0x00 },
+ { 0x22, 0x7A9, 0x40 },
+ { 0x22, 0x7AA, 0x00 },
+ { 0x22, 0x7AB, 0x00 },
+ { 0x22, 0x7AC, 0x33 },
+ { 0x22, 0x7AD, 0x80 },
+ /* common */
+ { 0x22, 0x782, 0xC1 },
+ { 0x22, 0x771, 0x2C },
+ { 0x22, 0x772, 0x2C },
+ { 0x22, 0x788, 0x04 },
+ { 0x01, 0x7B0, 0x08 },
+ {}
+};
+
+static const struct hda_fixup stac92hd83xxx_fixups[] = {
+ [STAC_92HD83XXX_REF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = ref92hd83xxx_pin_configs,
+ },
+ [STAC_92HD83XXX_PWR_REF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = ref92hd83xxx_pin_configs,
+ },
+ [STAC_DELL_S14] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_s14_pin_configs,
+ },
+ [STAC_DELL_VOSTRO_3500] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_vostro_3500_pin_configs,
+ },
+ [STAC_92HD83XXX_HP_cNB11_INTQUAD] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = hp_cNB11_intquad_pin_configs,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_92HD83XXX_HP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_hp,
+ },
+ [STAC_HP_DV7_4000] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = hp_dv7_4000_pin_configs,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_HP_ZEPHYR] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_hp_zephyr,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_92HD83XXX_HP_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_hp_led,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_92HD83XXX_HP_INV_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_hp_inv_led,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_92HD83XXX_HP_MIC_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_hp_mic_led,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_HP_LED_GPIO10] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_hp_led_gpio10,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP,
+ },
+ [STAC_92HD83XXX_HEADSET_JACK] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_headset_jack,
+ },
+ [STAC_HP_ENVY_BASS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x0f, 0x90170111 },
+ {}
+ },
+ },
+ [STAC_HP_BNB13_EQ] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = hp_bnb13_eq_verbs,
+ .chained = true,
+ .chain_id = STAC_92HD83XXX_HP_MIC_LED,
+ },
+ [STAC_HP_ENVY_TS_BASS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x10, 0x92170111 },
+ {}
+ },
+ },
+ [STAC_HP_ENVY_TS_DAC_BIND] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = hp_envy_ts_fixup_dac_bind,
+ .chained = true,
+ .chain_id = STAC_HP_ENVY_TS_BASS,
+ },
+ [STAC_92HD83XXX_GPIO10_EAPD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd83xxx_fixup_gpio10_eapd,
+ },
+};
+
+static const struct hda_model_fixup stac92hd83xxx_models[] = {
+ { .id = STAC_92HD83XXX_REF, .name = "ref" },
+ { .id = STAC_92HD83XXX_PWR_REF, .name = "mic-ref" },
+ { .id = STAC_DELL_S14, .name = "dell-s14" },
+ { .id = STAC_DELL_VOSTRO_3500, .name = "dell-vostro-3500" },
+ { .id = STAC_92HD83XXX_HP_cNB11_INTQUAD, .name = "hp_cNB11_intquad" },
+ { .id = STAC_HP_DV7_4000, .name = "hp-dv7-4000" },
+ { .id = STAC_HP_ZEPHYR, .name = "hp-zephyr" },
+ { .id = STAC_92HD83XXX_HP_LED, .name = "hp-led" },
+ { .id = STAC_92HD83XXX_HP_INV_LED, .name = "hp-inv-led" },
+ { .id = STAC_92HD83XXX_HP_MIC_LED, .name = "hp-mic-led" },
+ { .id = STAC_92HD83XXX_HEADSET_JACK, .name = "headset-jack" },
+ { .id = STAC_HP_ENVY_BASS, .name = "hp-envy-bass" },
+ { .id = STAC_HP_BNB13_EQ, .name = "hp-bnb13-eq" },
+ { .id = STAC_HP_ENVY_TS_BASS, .name = "hp-envy-ts-bass" },
+ {}
+};
+
+static const struct snd_pci_quirk stac92hd83xxx_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_92HD83XXX_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_92HD83XXX_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02ba,
+ "unknown Dell", STAC_DELL_S14),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0532,
+ "Dell Latitude E6230", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0533,
+ "Dell Latitude E6330", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0534,
+ "Dell Latitude E6430", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0535,
+ "Dell Latitude E6530", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x053c,
+ "Dell Latitude E5430", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x053d,
+ "Dell Latitude E5530", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0549,
+ "Dell Latitude E5430", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x057d,
+ "Dell Latitude E6430s", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0584,
+ "Dell Latitude E6430U", STAC_92HD83XXX_HEADSET_JACK),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x1028,
+ "Dell Vostro 3500", STAC_DELL_VOSTRO_3500),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1656,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1657,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1658,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1659,
+ "HP Pavilion dv7", STAC_HP_DV7_4000),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x165A,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x165B,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1888,
+ "HP Envy Spectre", STAC_HP_ENVY_BASS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1899,
+ "HP Folio 13", STAC_HP_LED_GPIO10),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x18df,
+ "HP Folio", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x18F8,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1909,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x190A,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x190e,
+ "HP ENVY TS", STAC_HP_ENVY_TS_BASS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1967,
+ "HP ENVY TS", STAC_HP_ENVY_TS_DAC_BIND),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1940,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1941,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1942,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1943,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1944,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1945,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1946,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1948,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1949,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194A,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194B,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194C,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194E,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194F,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1950,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1951,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x195A,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x195B,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x195C,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1991,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2103,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2104,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2105,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2106,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2107,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2108,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2109,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x210A,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x210B,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211C,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211D,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211E,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211F,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2120,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2121,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2122,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2123,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x213E,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x213F,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2140,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B2,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B3,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B5,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B6,
+ "HP bNB13", STAC_HP_BNB13_EQ),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x1900,
+ "HP", STAC_92HD83XXX_HP_MIC_LED),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x2000,
+ "HP", STAC_92HD83XXX_HP_MIC_LED),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x2100,
+ "HP", STAC_92HD83XXX_HP_MIC_LED),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3388,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3389,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355B,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355C,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355D,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355E,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355F,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3560,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x358B,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x358C,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x358D,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3591,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3592,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3593,
+ "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3561,
+ "HP", STAC_HP_ZEPHYR),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3660,
+ "HP Mini", STAC_92HD83XXX_HP_LED),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x144E,
+ "HP Pavilion dv5", STAC_92HD83XXX_HP_INV_LED),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x148a,
+ "HP Mini", STAC_92HD83XXX_HP_LED),
+ SND_PCI_QUIRK_VENDOR(PCI_VENDOR_ID_HP, "HP", STAC_92HD83XXX_HP),
+ /* match both for 0xfa91 and 0xfa93 */
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_TOSHIBA, 0xfffd, 0xfa91,
+ "Toshiba Satellite S50D", STAC_92HD83XXX_GPIO10_EAPD),
+ {} /* terminator */
+};
+
+/* HP dv7 bass switch - GPIO5 */
+#define stac_hp_bass_gpio_info snd_ctl_boolean_mono_info
+static int stac_hp_bass_gpio_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct sigmatel_spec *spec = codec->spec;
+ ucontrol->value.integer.value[0] = !!(spec->gpio_data & 0x20);
+ return 0;
+}
+
+static int stac_hp_bass_gpio_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct sigmatel_spec *spec = codec->spec;
+ unsigned int gpio_data;
+
+ gpio_data = (spec->gpio_data & ~0x20) |
+ (ucontrol->value.integer.value[0] ? 0x20 : 0);
+ if (gpio_data == spec->gpio_data)
+ return 0;
+ spec->gpio_data = gpio_data;
+ stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
+ return 1;
+}
+
+static const struct snd_kcontrol_new stac_hp_bass_sw_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = stac_hp_bass_gpio_info,
+ .get = stac_hp_bass_gpio_get,
+ .put = stac_hp_bass_gpio_put,
+};
+
+static int stac_add_hp_bass_switch(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (!snd_hda_gen_add_kctl(&spec->gen, "Bass Speaker Playback Switch",
+ &stac_hp_bass_sw_ctrl))
+ return -ENOMEM;
+
+ spec->gpio_mask |= 0x20;
+ spec->gpio_dir |= 0x20;
+ spec->gpio_data |= 0x20;
+ return 0;
+}
+
+static const struct hda_pintbl ref92hd71bxx_pin_configs[] = {
+ { 0x0a, 0x02214030 },
+ { 0x0b, 0x02a19040 },
+ { 0x0c, 0x01a19020 },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x0181302e },
+ { 0x0f, 0x01014010 },
+ { 0x14, 0x01019020 },
+ { 0x18, 0x90a000f0 },
+ { 0x19, 0x90a000f0 },
+ { 0x1e, 0x01452050 },
+ { 0x1f, 0x01452050 },
+ {}
+};
+
+static const struct hda_pintbl dell_m4_1_pin_configs[] = {
+ { 0x0a, 0x0421101f },
+ { 0x0b, 0x04a11221 },
+ { 0x0c, 0x40f000f0 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x23a1902e },
+ { 0x0f, 0x23014250 },
+ { 0x14, 0x40f000f0 },
+ { 0x18, 0x90a000f0 },
+ { 0x19, 0x40f000f0 },
+ { 0x1e, 0x4f0000f0 },
+ { 0x1f, 0x4f0000f0 },
+ {}
+};
+
+static const struct hda_pintbl dell_m4_2_pin_configs[] = {
+ { 0x0a, 0x0421101f },
+ { 0x0b, 0x04a11221 },
+ { 0x0c, 0x90a70330 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x23a1902e },
+ { 0x0f, 0x23014250 },
+ { 0x14, 0x40f000f0 },
+ { 0x18, 0x40f000f0 },
+ { 0x19, 0x40f000f0 },
+ { 0x1e, 0x044413b0 },
+ { 0x1f, 0x044413b0 },
+ {}
+};
+
+static const struct hda_pintbl dell_m4_3_pin_configs[] = {
+ { 0x0a, 0x0421101f },
+ { 0x0b, 0x04a11221 },
+ { 0x0c, 0x90a70330 },
+ { 0x0d, 0x90170110 },
+ { 0x0e, 0x40f000f0 },
+ { 0x0f, 0x40f000f0 },
+ { 0x14, 0x40f000f0 },
+ { 0x18, 0x90a000f0 },
+ { 0x19, 0x40f000f0 },
+ { 0x1e, 0x044413b0 },
+ { 0x1f, 0x044413b0 },
+ {}
+};
+
+static void stac92hd71bxx_fixup_ref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ snd_hda_apply_pincfgs(codec, ref92hd71bxx_pin_configs);
+ spec->gpio_mask = spec->gpio_dir = spec->gpio_data = 0;
+}
+
+static void stac92hd71bxx_fixup_hp_m4(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ struct hda_jack_callback *jack;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ /* Enable VREF power saving on GPIO1 detect */
+ snd_hda_codec_write_cache(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x02);
+ jack = snd_hda_jack_detect_enable_callback(codec, codec->core.afg,
+ stac_vref_event);
+ if (!IS_ERR(jack))
+ jack->private_data = 0x02;
+
+ spec->gpio_mask |= 0x02;
+
+ /* enable internal microphone */
+ snd_hda_codec_set_pincfg(codec, 0x0e, 0x01813040);
+}
+
+static void stac92hd71bxx_fixup_hp_dv4(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ spec->gpio_led = 0x01;
+}
+
+static void stac92hd71bxx_fixup_hp_dv5(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ unsigned int cap;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_codec_set_pincfg(codec, 0x0d, 0x90170010);
+ break;
+
+ case HDA_FIXUP_ACT_PROBE:
+ /* enable bass on HP dv7 */
+ cap = snd_hda_param_read(codec, 0x1, AC_PAR_GPIO_CAP);
+ cap &= AC_GPIO_IO_COUNT;
+ if (cap >= 6)
+ stac_add_hp_bass_switch(codec);
+ break;
+ }
+}
+
+static void stac92hd71bxx_fixup_hp_hdx(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ spec->gpio_led = 0x08;
+}
+
+static bool is_hp_output(struct hda_codec *codec, hda_nid_t pin)
+{
+ unsigned int pin_cfg = snd_hda_codec_get_pincfg(codec, pin);
+
+ /* count line-out, too, as BIOS sets often so */
+ return get_defcfg_connect(pin_cfg) != AC_JACK_PORT_NONE &&
+ (get_defcfg_device(pin_cfg) == AC_JACK_LINE_OUT ||
+ get_defcfg_device(pin_cfg) == AC_JACK_HP_OUT);
+}
+
+static void fixup_hp_headphone(struct hda_codec *codec, hda_nid_t pin)
+{
+ unsigned int pin_cfg = snd_hda_codec_get_pincfg(codec, pin);
+
+ /* It was changed in the BIOS to just satisfy MS DTM.
+ * Lets turn it back into follower HP
+ */
+ pin_cfg = (pin_cfg & (~AC_DEFCFG_DEVICE)) |
+ (AC_JACK_HP_OUT << AC_DEFCFG_DEVICE_SHIFT);
+ pin_cfg = (pin_cfg & (~(AC_DEFCFG_DEF_ASSOC | AC_DEFCFG_SEQUENCE))) |
+ 0x1f;
+ snd_hda_codec_set_pincfg(codec, pin, pin_cfg);
+}
+
+static void stac92hd71bxx_fixup_hp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ /* when both output A and F are assigned, these are supposedly
+ * dock and built-in headphones; fix both pin configs
+ */
+ if (is_hp_output(codec, 0x0a) && is_hp_output(codec, 0x0f)) {
+ fixup_hp_headphone(codec, 0x0a);
+ fixup_hp_headphone(codec, 0x0f);
+ }
+
+ if (find_mute_led_cfg(codec, 1))
+ codec_dbg(codec, "mute LED gpio %d polarity %d\n",
+ spec->gpio_led,
+ spec->gpio_led_polarity);
+
+}
+
+static const struct hda_fixup stac92hd71bxx_fixups[] = {
+ [STAC_92HD71BXX_REF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd71bxx_fixup_ref,
+ },
+ [STAC_DELL_M4_1] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_m4_1_pin_configs,
+ },
+ [STAC_DELL_M4_2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_m4_2_pin_configs,
+ },
+ [STAC_DELL_M4_3] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_m4_3_pin_configs,
+ },
+ [STAC_HP_M4] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd71bxx_fixup_hp_m4,
+ .chained = true,
+ .chain_id = STAC_92HD71BXX_HP,
+ },
+ [STAC_HP_DV4] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd71bxx_fixup_hp_dv4,
+ .chained = true,
+ .chain_id = STAC_HP_DV5,
+ },
+ [STAC_HP_DV5] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd71bxx_fixup_hp_dv5,
+ .chained = true,
+ .chain_id = STAC_92HD71BXX_HP,
+ },
+ [STAC_HP_HDX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd71bxx_fixup_hp_hdx,
+ .chained = true,
+ .chain_id = STAC_92HD71BXX_HP,
+ },
+ [STAC_92HD71BXX_HP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd71bxx_fixup_hp,
+ },
+};
+
+static const struct hda_model_fixup stac92hd71bxx_models[] = {
+ { .id = STAC_92HD71BXX_REF, .name = "ref" },
+ { .id = STAC_DELL_M4_1, .name = "dell-m4-1" },
+ { .id = STAC_DELL_M4_2, .name = "dell-m4-2" },
+ { .id = STAC_DELL_M4_3, .name = "dell-m4-3" },
+ { .id = STAC_HP_M4, .name = "hp-m4" },
+ { .id = STAC_HP_DV4, .name = "hp-dv4" },
+ { .id = STAC_HP_DV5, .name = "hp-dv5" },
+ { .id = STAC_HP_HDX, .name = "hp-hdx" },
+ { .id = STAC_HP_DV4, .name = "hp-dv4-1222nr" },
+ {}
+};
+
+static const struct snd_pci_quirk stac92hd71bxx_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_92HD71BXX_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_92HD71BXX_REF),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x1720,
+ "HP", STAC_HP_DV5),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3080,
+ "HP", STAC_HP_DV5),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x30f0,
+ "HP dv4-7", STAC_HP_DV4),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3600,
+ "HP dv4-7", STAC_HP_DV5),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3610,
+ "HP HDX", STAC_HP_HDX), /* HDX18 */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361a,
+ "HP mini 1000", STAC_HP_M4),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361b,
+ "HP HDX", STAC_HP_HDX), /* HDX16 */
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3620,
+ "HP dv6", STAC_HP_DV5),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3061,
+ "HP dv6", STAC_HP_DV5), /* HP dv6-1110ax */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x363e,
+ "HP DV6", STAC_HP_DV5),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x7010,
+ "HP", STAC_HP_DV5),
+ SND_PCI_QUIRK_VENDOR(PCI_VENDOR_ID_HP, "HP", STAC_92HD71BXX_HP),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0233,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0234,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0250,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024f,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024d,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0251,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0277,
+ "unknown Dell", STAC_DELL_M4_1),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0263,
+ "unknown Dell", STAC_DELL_M4_2),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0265,
+ "unknown Dell", STAC_DELL_M4_2),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0262,
+ "unknown Dell", STAC_DELL_M4_2),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0264,
+ "unknown Dell", STAC_DELL_M4_2),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02aa,
+ "unknown Dell", STAC_DELL_M4_3),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl ref922x_pin_configs[] = {
+ { 0x0a, 0x01014010 },
+ { 0x0b, 0x01016011 },
+ { 0x0c, 0x01012012 },
+ { 0x0d, 0x0221401f },
+ { 0x0e, 0x01813122 },
+ { 0x0f, 0x01011014 },
+ { 0x10, 0x01441030 },
+ { 0x11, 0x01c41030 },
+ { 0x15, 0x40000100 },
+ { 0x1b, 0x40000100 },
+ {}
+};
+
+/*
+ STAC 922X pin configs for
+ 102801A7
+ 102801AB
+ 102801A9
+ 102801D1
+ 102801D2
+*/
+static const struct hda_pintbl dell_922x_d81_pin_configs[] = {
+ { 0x0a, 0x02214030 },
+ { 0x0b, 0x01a19021 },
+ { 0x0c, 0x01111012 },
+ { 0x0d, 0x01114010 },
+ { 0x0e, 0x02a19020 },
+ { 0x0f, 0x01117011 },
+ { 0x10, 0x400001f0 },
+ { 0x11, 0x400001f1 },
+ { 0x15, 0x01813122 },
+ { 0x1b, 0x400001f2 },
+ {}
+};
+
+/*
+ STAC 922X pin configs for
+ 102801AC
+ 102801D0
+*/
+static const struct hda_pintbl dell_922x_d82_pin_configs[] = {
+ { 0x0a, 0x02214030 },
+ { 0x0b, 0x01a19021 },
+ { 0x0c, 0x01111012 },
+ { 0x0d, 0x01114010 },
+ { 0x0e, 0x02a19020 },
+ { 0x0f, 0x01117011 },
+ { 0x10, 0x01451140 },
+ { 0x11, 0x400001f0 },
+ { 0x15, 0x01813122 },
+ { 0x1b, 0x400001f1 },
+ {}
+};
+
+/*
+ STAC 922X pin configs for
+ 102801BF
+*/
+static const struct hda_pintbl dell_922x_m81_pin_configs[] = {
+ { 0x0a, 0x0321101f },
+ { 0x0b, 0x01112024 },
+ { 0x0c, 0x01111222 },
+ { 0x0d, 0x91174220 },
+ { 0x0e, 0x03a11050 },
+ { 0x0f, 0x01116221 },
+ { 0x10, 0x90a70330 },
+ { 0x11, 0x01452340 },
+ { 0x15, 0x40C003f1 },
+ { 0x1b, 0x405003f0 },
+ {}
+};
+
+/*
+ STAC 9221 A1 pin configs for
+ 102801D7 (Dell XPS M1210)
+*/
+static const struct hda_pintbl dell_922x_m82_pin_configs[] = {
+ { 0x0a, 0x02211211 },
+ { 0x0b, 0x408103ff },
+ { 0x0c, 0x02a1123e },
+ { 0x0d, 0x90100310 },
+ { 0x0e, 0x408003f1 },
+ { 0x0f, 0x0221121f },
+ { 0x10, 0x03451340 },
+ { 0x11, 0x40c003f2 },
+ { 0x15, 0x508003f3 },
+ { 0x1b, 0x405003f4 },
+ {}
+};
+
+static const struct hda_pintbl d945gtp3_pin_configs[] = {
+ { 0x0a, 0x0221401f },
+ { 0x0b, 0x01a19022 },
+ { 0x0c, 0x01813021 },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x40000100 },
+ { 0x0f, 0x40000100 },
+ { 0x10, 0x40000100 },
+ { 0x11, 0x40000100 },
+ { 0x15, 0x02a19120 },
+ { 0x1b, 0x40000100 },
+ {}
+};
+
+static const struct hda_pintbl d945gtp5_pin_configs[] = {
+ { 0x0a, 0x0221401f },
+ { 0x0b, 0x01011012 },
+ { 0x0c, 0x01813024 },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x01a19021 },
+ { 0x0f, 0x01016011 },
+ { 0x10, 0x01452130 },
+ { 0x11, 0x40000100 },
+ { 0x15, 0x02a19320 },
+ { 0x1b, 0x40000100 },
+ {}
+};
+
+static const struct hda_pintbl intel_mac_v1_pin_configs[] = {
+ { 0x0a, 0x0121e21f },
+ { 0x0b, 0x400000ff },
+ { 0x0c, 0x9017e110 },
+ { 0x0d, 0x400000fd },
+ { 0x0e, 0x400000fe },
+ { 0x0f, 0x0181e020 },
+ { 0x10, 0x1145e030 },
+ { 0x11, 0x11c5e240 },
+ { 0x15, 0x400000fc },
+ { 0x1b, 0x400000fb },
+ {}
+};
+
+static const struct hda_pintbl intel_mac_v2_pin_configs[] = {
+ { 0x0a, 0x0121e21f },
+ { 0x0b, 0x90a7012e },
+ { 0x0c, 0x9017e110 },
+ { 0x0d, 0x400000fd },
+ { 0x0e, 0x400000fe },
+ { 0x0f, 0x0181e020 },
+ { 0x10, 0x1145e230 },
+ { 0x11, 0x500000fa },
+ { 0x15, 0x400000fc },
+ { 0x1b, 0x400000fb },
+ {}
+};
+
+static const struct hda_pintbl intel_mac_v3_pin_configs[] = {
+ { 0x0a, 0x0121e21f },
+ { 0x0b, 0x90a7012e },
+ { 0x0c, 0x9017e110 },
+ { 0x0d, 0x400000fd },
+ { 0x0e, 0x400000fe },
+ { 0x0f, 0x0181e020 },
+ { 0x10, 0x1145e230 },
+ { 0x11, 0x11c5e240 },
+ { 0x15, 0x400000fc },
+ { 0x1b, 0x400000fb },
+ {}
+};
+
+static const struct hda_pintbl intel_mac_v4_pin_configs[] = {
+ { 0x0a, 0x0321e21f },
+ { 0x0b, 0x03a1e02e },
+ { 0x0c, 0x9017e110 },
+ { 0x0d, 0x9017e11f },
+ { 0x0e, 0x400000fe },
+ { 0x0f, 0x0381e020 },
+ { 0x10, 0x1345e230 },
+ { 0x11, 0x13c5e240 },
+ { 0x15, 0x400000fc },
+ { 0x1b, 0x400000fb },
+ {}
+};
+
+static const struct hda_pintbl intel_mac_v5_pin_configs[] = {
+ { 0x0a, 0x0321e21f },
+ { 0x0b, 0x03a1e02e },
+ { 0x0c, 0x9017e110 },
+ { 0x0d, 0x9017e11f },
+ { 0x0e, 0x400000fe },
+ { 0x0f, 0x0381e020 },
+ { 0x10, 0x1345e230 },
+ { 0x11, 0x13c5e240 },
+ { 0x15, 0x400000fc },
+ { 0x1b, 0x400000fb },
+ {}
+};
+
+static const struct hda_pintbl ecs202_pin_configs[] = {
+ { 0x0a, 0x0221401f },
+ { 0x0b, 0x02a19020 },
+ { 0x0c, 0x01a19020 },
+ { 0x0d, 0x01114010 },
+ { 0x0e, 0x408000f0 },
+ { 0x0f, 0x01813022 },
+ { 0x10, 0x074510a0 },
+ { 0x11, 0x40c400f1 },
+ { 0x15, 0x9037012e },
+ { 0x1b, 0x40e000f2 },
+ {}
+};
+
+/* codec SSIDs for Intel Mac sharing the same PCI SSID 8384:7680 */
+static const struct snd_pci_quirk stac922x_intel_mac_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x0000, 0x0100, "Mac Mini", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x0800, "Mac", STAC_INTEL_MAC_V1),
+ SND_PCI_QUIRK(0x106b, 0x0600, "Mac", STAC_INTEL_MAC_V2),
+ SND_PCI_QUIRK(0x106b, 0x0700, "Mac", STAC_INTEL_MAC_V2),
+ SND_PCI_QUIRK(0x106b, 0x0e00, "Mac", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x0f00, "Mac", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x1600, "Mac", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x1700, "Mac", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x0200, "Mac", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x1e00, "Mac", STAC_INTEL_MAC_V3),
+ SND_PCI_QUIRK(0x106b, 0x1a00, "Mac", STAC_INTEL_MAC_V4),
+ SND_PCI_QUIRK(0x106b, 0x0a00, "Mac", STAC_INTEL_MAC_V5),
+ SND_PCI_QUIRK(0x106b, 0x2200, "Mac", STAC_INTEL_MAC_V5),
+ {}
+};
+
+static const struct hda_fixup stac922x_fixups[];
+
+/* remap the fixup from codec SSID and apply it */
+static void stac922x_fixup_intel_mac_auto(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
+ snd_hda_pick_fixup(codec, NULL, stac922x_intel_mac_fixup_tbl,
+ stac922x_fixups);
+ if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET)
+ snd_hda_apply_fixup(codec, action);
+}
+
+static void stac922x_fixup_intel_mac_gpio(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ spec->gpio_mask = spec->gpio_dir = 0x03;
+ spec->gpio_data = 0x03;
+ }
+}
+
+static const struct hda_fixup stac922x_fixups[] = {
+ [STAC_D945_REF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = ref922x_pin_configs,
+ },
+ [STAC_D945GTP3] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = d945gtp3_pin_configs,
+ },
+ [STAC_D945GTP5] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = d945gtp5_pin_configs,
+ },
+ [STAC_INTEL_MAC_AUTO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac922x_fixup_intel_mac_auto,
+ },
+ [STAC_INTEL_MAC_V1] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = intel_mac_v1_pin_configs,
+ .chained = true,
+ .chain_id = STAC_922X_INTEL_MAC_GPIO,
+ },
+ [STAC_INTEL_MAC_V2] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = intel_mac_v2_pin_configs,
+ .chained = true,
+ .chain_id = STAC_922X_INTEL_MAC_GPIO,
+ },
+ [STAC_INTEL_MAC_V3] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = intel_mac_v3_pin_configs,
+ .chained = true,
+ .chain_id = STAC_922X_INTEL_MAC_GPIO,
+ },
+ [STAC_INTEL_MAC_V4] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = intel_mac_v4_pin_configs,
+ .chained = true,
+ .chain_id = STAC_922X_INTEL_MAC_GPIO,
+ },
+ [STAC_INTEL_MAC_V5] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = intel_mac_v5_pin_configs,
+ .chained = true,
+ .chain_id = STAC_922X_INTEL_MAC_GPIO,
+ },
+ [STAC_922X_INTEL_MAC_GPIO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac922x_fixup_intel_mac_gpio,
+ },
+ [STAC_ECS_202] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = ecs202_pin_configs,
+ },
+ [STAC_922X_DELL_D81] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_922x_d81_pin_configs,
+ },
+ [STAC_922X_DELL_D82] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_922x_d82_pin_configs,
+ },
+ [STAC_922X_DELL_M81] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_922x_m81_pin_configs,
+ },
+ [STAC_922X_DELL_M82] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_922x_m82_pin_configs,
+ },
+};
+
+static const struct hda_model_fixup stac922x_models[] = {
+ { .id = STAC_D945_REF, .name = "ref" },
+ { .id = STAC_D945GTP5, .name = "5stack" },
+ { .id = STAC_D945GTP3, .name = "3stack" },
+ { .id = STAC_INTEL_MAC_V1, .name = "intel-mac-v1" },
+ { .id = STAC_INTEL_MAC_V2, .name = "intel-mac-v2" },
+ { .id = STAC_INTEL_MAC_V3, .name = "intel-mac-v3" },
+ { .id = STAC_INTEL_MAC_V4, .name = "intel-mac-v4" },
+ { .id = STAC_INTEL_MAC_V5, .name = "intel-mac-v5" },
+ { .id = STAC_INTEL_MAC_AUTO, .name = "intel-mac-auto" },
+ { .id = STAC_ECS_202, .name = "ecs202" },
+ { .id = STAC_922X_DELL_D81, .name = "dell-d81" },
+ { .id = STAC_922X_DELL_D82, .name = "dell-d82" },
+ { .id = STAC_922X_DELL_M81, .name = "dell-m81" },
+ { .id = STAC_922X_DELL_M82, .name = "dell-m82" },
+ /* for backward compatibility */
+ { .id = STAC_INTEL_MAC_V3, .name = "macmini" },
+ { .id = STAC_INTEL_MAC_V5, .name = "macbook" },
+ { .id = STAC_INTEL_MAC_V3, .name = "macbook-pro-v1" },
+ { .id = STAC_INTEL_MAC_V3, .name = "macbook-pro" },
+ { .id = STAC_INTEL_MAC_V2, .name = "imac-intel" },
+ { .id = STAC_INTEL_MAC_V3, .name = "imac-intel-20" },
+ {}
+};
+
+static const struct snd_pci_quirk stac922x_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_D945_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_D945_REF),
+ /* Intel 945G based systems */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0101,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0202,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0606,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0601,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0111,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1115,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1116,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1117,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1118,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1119,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x8826,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5049,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5055,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5048,
+ "Intel D945G", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0110,
+ "Intel D945G", STAC_D945GTP3),
+ /* Intel D945G 5-stack systems */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0404,
+ "Intel D945G", STAC_D945GTP5),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0303,
+ "Intel D945G", STAC_D945GTP5),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0013,
+ "Intel D945G", STAC_D945GTP5),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0417,
+ "Intel D945G", STAC_D945GTP5),
+ /* Intel 945P based systems */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0b0b,
+ "Intel D945P", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0112,
+ "Intel D945P", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0d0d,
+ "Intel D945P", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0909,
+ "Intel D945P", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0505,
+ "Intel D945P", STAC_D945GTP3),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0707,
+ "Intel D945P", STAC_D945GTP5),
+ /* other intel */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0204,
+ "Intel D945", STAC_D945_REF),
+ /* other systems */
+
+ /* Apple Intel Mac (Mac Mini, MacBook, MacBook Pro...) */
+ SND_PCI_QUIRK(0x8384, 0x7680, "Mac", STAC_INTEL_MAC_AUTO),
+
+ /* Dell systems */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a7,
+ "unknown Dell", STAC_922X_DELL_D81),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a9,
+ "unknown Dell", STAC_922X_DELL_D81),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ab,
+ "unknown Dell", STAC_922X_DELL_D81),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ac,
+ "unknown Dell", STAC_922X_DELL_D82),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bf,
+ "unknown Dell", STAC_922X_DELL_M81),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d0,
+ "unknown Dell", STAC_922X_DELL_D82),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d1,
+ "unknown Dell", STAC_922X_DELL_D81),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d2,
+ "unknown Dell", STAC_922X_DELL_D81),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d7,
+ "Dell XPS M1210", STAC_922X_DELL_M82),
+ /* ECS/PC Chips boards */
+ SND_PCI_QUIRK_MASK(0x1019, 0xf000, 0x2000,
+ "ECS/PC chips", STAC_ECS_202),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl ref927x_pin_configs[] = {
+ { 0x0a, 0x02214020 },
+ { 0x0b, 0x02a19080 },
+ { 0x0c, 0x0181304e },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x01a19040 },
+ { 0x0f, 0x01011012 },
+ { 0x10, 0x01016011 },
+ { 0x11, 0x0101201f },
+ { 0x12, 0x183301f0 },
+ { 0x13, 0x18a001f0 },
+ { 0x14, 0x18a001f0 },
+ { 0x21, 0x01442070 },
+ { 0x22, 0x01c42190 },
+ { 0x23, 0x40000100 },
+ {}
+};
+
+static const struct hda_pintbl d965_3st_pin_configs[] = {
+ { 0x0a, 0x0221401f },
+ { 0x0b, 0x02a19120 },
+ { 0x0c, 0x40000100 },
+ { 0x0d, 0x01014011 },
+ { 0x0e, 0x01a19021 },
+ { 0x0f, 0x01813024 },
+ { 0x10, 0x40000100 },
+ { 0x11, 0x40000100 },
+ { 0x12, 0x40000100 },
+ { 0x13, 0x40000100 },
+ { 0x14, 0x40000100 },
+ { 0x21, 0x40000100 },
+ { 0x22, 0x40000100 },
+ { 0x23, 0x40000100 },
+ {}
+};
+
+static const struct hda_pintbl d965_5st_pin_configs[] = {
+ { 0x0a, 0x02214020 },
+ { 0x0b, 0x02a19080 },
+ { 0x0c, 0x0181304e },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x01a19040 },
+ { 0x0f, 0x01011012 },
+ { 0x10, 0x01016011 },
+ { 0x11, 0x40000100 },
+ { 0x12, 0x40000100 },
+ { 0x13, 0x40000100 },
+ { 0x14, 0x40000100 },
+ { 0x21, 0x01442070 },
+ { 0x22, 0x40000100 },
+ { 0x23, 0x40000100 },
+ {}
+};
+
+static const struct hda_pintbl d965_5st_no_fp_pin_configs[] = {
+ { 0x0a, 0x40000100 },
+ { 0x0b, 0x40000100 },
+ { 0x0c, 0x0181304e },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x01a19040 },
+ { 0x0f, 0x01011012 },
+ { 0x10, 0x01016011 },
+ { 0x11, 0x40000100 },
+ { 0x12, 0x40000100 },
+ { 0x13, 0x40000100 },
+ { 0x14, 0x40000100 },
+ { 0x21, 0x01442070 },
+ { 0x22, 0x40000100 },
+ { 0x23, 0x40000100 },
+ {}
+};
+
+static const struct hda_pintbl dell_3st_pin_configs[] = {
+ { 0x0a, 0x02211230 },
+ { 0x0b, 0x02a11220 },
+ { 0x0c, 0x01a19040 },
+ { 0x0d, 0x01114210 },
+ { 0x0e, 0x01111212 },
+ { 0x0f, 0x01116211 },
+ { 0x10, 0x01813050 },
+ { 0x11, 0x01112214 },
+ { 0x12, 0x403003fa },
+ { 0x13, 0x90a60040 },
+ { 0x14, 0x90a60040 },
+ { 0x21, 0x404003fb },
+ { 0x22, 0x40c003fc },
+ { 0x23, 0x40000100 },
+ {}
+};
+
+static void stac927x_fixup_ref_no_jd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ /* no jack detecion for ref-no-jd model */
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ codec->no_jack_detect = 1;
+}
+
+static void stac927x_fixup_ref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ snd_hda_apply_pincfgs(codec, ref927x_pin_configs);
+ spec->eapd_mask = spec->gpio_mask = 0;
+ spec->gpio_dir = spec->gpio_data = 0;
+ }
+}
+
+static void stac927x_fixup_dell_dmic(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ if (codec->core.subsystem_id != 0x1028022f) {
+ /* GPIO2 High = Enable EAPD */
+ spec->eapd_mask = spec->gpio_mask = 0x04;
+ spec->gpio_dir = spec->gpio_data = 0x04;
+ }
+
+ snd_hda_add_verbs(codec, dell_3st_core_init);
+ spec->volknob_init = 1;
+}
+
+static void stac927x_fixup_volknob(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ snd_hda_add_verbs(codec, stac927x_volknob_core_init);
+ spec->volknob_init = 1;
+ }
+}
+
+static const struct hda_fixup stac927x_fixups[] = {
+ [STAC_D965_REF_NO_JD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac927x_fixup_ref_no_jd,
+ .chained = true,
+ .chain_id = STAC_D965_REF,
+ },
+ [STAC_D965_REF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac927x_fixup_ref,
+ },
+ [STAC_D965_3ST] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = d965_3st_pin_configs,
+ .chained = true,
+ .chain_id = STAC_D965_VERBS,
+ },
+ [STAC_D965_5ST] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = d965_5st_pin_configs,
+ .chained = true,
+ .chain_id = STAC_D965_VERBS,
+ },
+ [STAC_D965_VERBS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = d965_core_init,
+ },
+ [STAC_D965_5ST_NO_FP] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = d965_5st_no_fp_pin_configs,
+ },
+ [STAC_NEMO_DEFAULT] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = nemo_pin_configs,
+ },
+ [STAC_DELL_3ST] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_3st_pin_configs,
+ .chained = true,
+ .chain_id = STAC_927X_DELL_DMIC,
+ },
+ [STAC_DELL_BIOS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* correct the front output jack as a hp out */
+ { 0x0f, 0x0221101f },
+ /* correct the front input jack as a mic */
+ { 0x0e, 0x02a79130 },
+ {}
+ },
+ .chained = true,
+ .chain_id = STAC_927X_DELL_DMIC,
+ },
+ [STAC_DELL_BIOS_AMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* configure the analog microphone on some laptops */
+ { 0x0c, 0x90a79130 },
+ {}
+ },
+ .chained = true,
+ .chain_id = STAC_DELL_BIOS,
+ },
+ [STAC_DELL_BIOS_SPDIF] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* correct the device field to SPDIF out */
+ { 0x21, 0x01442070 },
+ {}
+ },
+ .chained = true,
+ .chain_id = STAC_DELL_BIOS,
+ },
+ [STAC_927X_DELL_DMIC] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac927x_fixup_dell_dmic,
+ },
+ [STAC_927X_VOLKNOB] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac927x_fixup_volknob,
+ },
+};
+
+static const struct hda_model_fixup stac927x_models[] = {
+ { .id = STAC_D965_REF_NO_JD, .name = "ref-no-jd" },
+ { .id = STAC_D965_REF, .name = "ref" },
+ { .id = STAC_D965_3ST, .name = "3stack" },
+ { .id = STAC_D965_5ST, .name = "5stack" },
+ { .id = STAC_D965_5ST_NO_FP, .name = "5stack-no-fp" },
+ { .id = STAC_DELL_3ST, .name = "dell-3stack" },
+ { .id = STAC_DELL_BIOS, .name = "dell-bios" },
+ { .id = STAC_NEMO_DEFAULT, .name = "nemo-default" },
+ { .id = STAC_DELL_BIOS_AMIC, .name = "dell-bios-amic" },
+ { .id = STAC_927X_VOLKNOB, .name = "volknob" },
+ {}
+};
+
+static const struct snd_pci_quirk stac927x_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_D965_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_D965_REF),
+ /* Intel 946 based systems */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x3d01, "Intel D946", STAC_D965_3ST),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xa301, "Intel D946", STAC_D965_3ST),
+ /* 965 based 3 stack systems */
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2100,
+ "Intel D965", STAC_D965_3ST),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2000,
+ "Intel D965", STAC_D965_3ST),
+ /* Dell 3 stack systems */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01dd, "Dell Dimension E520", STAC_DELL_3ST),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ed, "Dell ", STAC_DELL_3ST),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f4, "Dell ", STAC_DELL_3ST),
+ /* Dell 3 stack systems with verb table in BIOS */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f3, "Dell Inspiron 1420", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f7, "Dell XPS M1730", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0227, "Dell Vostro 1400 ", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x022e, "Dell ", STAC_DELL_BIOS_SPDIF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x022f, "Dell Inspiron 1525", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0242, "Dell ", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0243, "Dell ", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02ff, "Dell ", STAC_DELL_BIOS),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0209, "Dell XPS 1330", STAC_DELL_BIOS_SPDIF),
+ /* 965 based 5 stack systems */
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2300,
+ "Intel D965", STAC_D965_5ST),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2500,
+ "Intel D965", STAC_D965_5ST),
+ /* Nemo */
+ SND_PCI_QUIRK(0x1888, 0x1000, "AmigaOne X1000", STAC_NEMO_DEFAULT),
+ /* volume-knob fixes */
+ SND_PCI_QUIRK_VENDOR(0x10cf, "FSC", STAC_927X_VOLKNOB),
+ {} /* terminator */
+};
+
+static const struct hda_pintbl ref9205_pin_configs[] = {
+ { 0x0a, 0x40000100 },
+ { 0x0b, 0x40000100 },
+ { 0x0c, 0x01016011 },
+ { 0x0d, 0x01014010 },
+ { 0x0e, 0x01813122 },
+ { 0x0f, 0x01a19021 },
+ { 0x14, 0x01019020 },
+ { 0x16, 0x40000100 },
+ { 0x17, 0x90a000f0 },
+ { 0x18, 0x90a000f0 },
+ { 0x21, 0x01441030 },
+ { 0x22, 0x01c41030 },
+ {}
+};
+
+/*
+ STAC 9205 pin configs for
+ 102801F1
+ 102801F2
+ 102801FC
+ 102801FD
+ 10280204
+ 1028021F
+ 10280228 (Dell Vostro 1500)
+ 10280229 (Dell Vostro 1700)
+*/
+static const struct hda_pintbl dell_9205_m42_pin_configs[] = {
+ { 0x0a, 0x0321101F },
+ { 0x0b, 0x03A11020 },
+ { 0x0c, 0x400003FA },
+ { 0x0d, 0x90170310 },
+ { 0x0e, 0x400003FB },
+ { 0x0f, 0x400003FC },
+ { 0x14, 0x400003FD },
+ { 0x16, 0x40F000F9 },
+ { 0x17, 0x90A60330 },
+ { 0x18, 0x400003FF },
+ { 0x21, 0x0144131F },
+ { 0x22, 0x40C003FE },
+ {}
+};
+
+/*
+ STAC 9205 pin configs for
+ 102801F9
+ 102801FA
+ 102801FE
+ 102801FF (Dell Precision M4300)
+ 10280206
+ 10280200
+ 10280201
+*/
+static const struct hda_pintbl dell_9205_m43_pin_configs[] = {
+ { 0x0a, 0x0321101f },
+ { 0x0b, 0x03a11020 },
+ { 0x0c, 0x90a70330 },
+ { 0x0d, 0x90170310 },
+ { 0x0e, 0x400000fe },
+ { 0x0f, 0x400000ff },
+ { 0x14, 0x400000fd },
+ { 0x16, 0x40f000f9 },
+ { 0x17, 0x400000fa },
+ { 0x18, 0x400000fc },
+ { 0x21, 0x0144131f },
+ { 0x22, 0x40c003f8 },
+ /* Enable SPDIF in/out */
+ { 0x1f, 0x01441030 },
+ { 0x20, 0x1c410030 },
+ {}
+};
+
+static const struct hda_pintbl dell_9205_m44_pin_configs[] = {
+ { 0x0a, 0x0421101f },
+ { 0x0b, 0x04a11020 },
+ { 0x0c, 0x400003fa },
+ { 0x0d, 0x90170310 },
+ { 0x0e, 0x400003fb },
+ { 0x0f, 0x400003fc },
+ { 0x14, 0x400003fd },
+ { 0x16, 0x400003f9 },
+ { 0x17, 0x90a60330 },
+ { 0x18, 0x400003ff },
+ { 0x21, 0x01441340 },
+ { 0x22, 0x40c003fe },
+ {}
+};
+
+static void stac9205_fixup_ref(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ snd_hda_apply_pincfgs(codec, ref9205_pin_configs);
+ /* SPDIF-In enabled */
+ spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0;
+ }
+}
+
+static void stac9205_fixup_dell_m43(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ struct hda_jack_callback *jack;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ snd_hda_apply_pincfgs(codec, dell_9205_m43_pin_configs);
+
+ /* Enable unsol response for GPIO4/Dock HP connection */
+ snd_hda_codec_write_cache(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x10);
+ jack = snd_hda_jack_detect_enable_callback(codec, codec->core.afg,
+ stac_vref_event);
+ if (!IS_ERR(jack))
+ jack->private_data = 0x01;
+
+ spec->gpio_dir = 0x0b;
+ spec->eapd_mask = 0x01;
+ spec->gpio_mask = 0x1b;
+ spec->gpio_mute = 0x10;
+ /* GPIO0 High = EAPD, GPIO1 Low = Headphone Mute,
+ * GPIO3 Low = DRM
+ */
+ spec->gpio_data = 0x01;
+ }
+}
+
+static void stac9205_fixup_eapd(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->eapd_switch = 0;
+}
+
+static const struct hda_fixup stac9205_fixups[] = {
+ [STAC_9205_REF] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac9205_fixup_ref,
+ },
+ [STAC_9205_DELL_M42] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_9205_m42_pin_configs,
+ },
+ [STAC_9205_DELL_M43] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac9205_fixup_dell_m43,
+ },
+ [STAC_9205_DELL_M44] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = dell_9205_m44_pin_configs,
+ },
+ [STAC_9205_EAPD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac9205_fixup_eapd,
+ },
+ {}
+};
+
+static const struct hda_model_fixup stac9205_models[] = {
+ { .id = STAC_9205_REF, .name = "ref" },
+ { .id = STAC_9205_DELL_M42, .name = "dell-m42" },
+ { .id = STAC_9205_DELL_M43, .name = "dell-m43" },
+ { .id = STAC_9205_DELL_M44, .name = "dell-m44" },
+ { .id = STAC_9205_EAPD, .name = "eapd" },
+ {}
+};
+
+static const struct snd_pci_quirk stac9205_fixup_tbl[] = {
+ /* SigmaTel reference board */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+ "DFI LanParty", STAC_9205_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xfb30,
+ "SigmaTel", STAC_9205_REF),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+ "DFI LanParty", STAC_9205_REF),
+ /* Dell */
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f1,
+ "unknown Dell", STAC_9205_DELL_M42),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f2,
+ "unknown Dell", STAC_9205_DELL_M42),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f8,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f9,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fa,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fc,
+ "unknown Dell", STAC_9205_DELL_M42),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fd,
+ "unknown Dell", STAC_9205_DELL_M42),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fe,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ff,
+ "Dell Precision M4300", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0204,
+ "unknown Dell", STAC_9205_DELL_M42),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0206,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021b,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021c,
+ "Dell Precision", STAC_9205_DELL_M43),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021f,
+ "Dell Inspiron", STAC_9205_DELL_M44),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0228,
+ "Dell Vostro 1500", STAC_9205_DELL_M42),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0229,
+ "Dell Vostro 1700", STAC_9205_DELL_M42),
+ /* Gateway */
+ SND_PCI_QUIRK(0x107b, 0x0560, "Gateway T6834c", STAC_9205_EAPD),
+ SND_PCI_QUIRK(0x107b, 0x0565, "Gateway T1616", STAC_9205_EAPD),
+ {} /* terminator */
+};
+
+static void stac92hd95_fixup_hp_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+
+ if (find_mute_led_cfg(codec, spec->default_polarity))
+ codec_dbg(codec, "mute LED gpio %d polarity %d\n",
+ spec->gpio_led,
+ spec->gpio_led_polarity);
+}
+
+static const struct hda_fixup stac92hd95_fixups[] = {
+ [STAC_92HD95_HP_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = stac92hd95_fixup_hp_led,
+ },
+ [STAC_92HD95_HP_BASS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ {0x1a, 0x795, 0x00}, /* HPF to 100Hz */
+ {}
+ },
+ .chained = true,
+ .chain_id = STAC_92HD95_HP_LED,
+ },
+};
+
+static const struct snd_pci_quirk stac92hd95_fixup_tbl[] = {
+ SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1911, "HP Spectre 13", STAC_92HD95_HP_BASS),
+ {} /* terminator */
+};
+
+static const struct hda_model_fixup stac92hd95_models[] = {
+ { .id = STAC_92HD95_HP_LED, .name = "hp-led" },
+ { .id = STAC_92HD95_HP_BASS, .name = "hp-bass" },
+ {}
+};
+
+
+static int stac_parse_auto_config(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ int err;
+ int flags = 0;
+
+ if (spec->headset_jack)
+ flags |= HDA_PINCFG_HEADSET_MIC;
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, flags);
+ if (err < 0)
+ return err;
+
+ /* add hooks */
+ spec->gen.pcm_playback_hook = stac_playback_pcm_hook;
+ spec->gen.pcm_capture_hook = stac_capture_pcm_hook;
+
+ spec->gen.automute_hook = stac_update_outputs;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
+ if (err < 0)
+ return err;
+
+ if (spec->vref_mute_led_nid) {
+ err = snd_hda_gen_fix_pin_power(codec, spec->vref_mute_led_nid);
+ if (err < 0)
+ return err;
+ }
+
+ /* setup analog beep controls */
+ if (spec->anabeep_nid > 0) {
+ err = stac_auto_create_beep_ctls(codec,
+ spec->anabeep_nid);
+ if (err < 0)
+ return err;
+ }
+
+ /* setup digital beep controls and input device */
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+ if (spec->gen.beep_nid) {
+ hda_nid_t nid = spec->gen.beep_nid;
+ unsigned int caps;
+
+ err = stac_auto_create_beep_ctls(codec, nid);
+ if (err < 0)
+ return err;
+ if (codec->beep) {
+ /* IDT/STAC codecs have linear beep tone parameter */
+ codec->beep->linear_tone = spec->linear_tone_beep;
+ /* keep power up while beep is enabled */
+ codec->beep->keep_power_at_enable = 1;
+ /* if no beep switch is available, make its own one */
+ caps = query_amp_caps(codec, nid, HDA_OUTPUT);
+ if (!(caps & AC_AMPCAP_MUTE)) {
+ err = stac_beep_switch_ctl(codec);
+ if (err < 0)
+ return err;
+ }
+ }
+ }
+#endif
+
+ if (spec->gpio_led)
+ snd_hda_gen_add_mute_led_cdev(codec, stac_vmaster_hook);
+
+ if (spec->aloopback_ctl &&
+ snd_hda_get_bool_hint(codec, "loopback") == 1) {
+ unsigned int wr_verb =
+ spec->aloopback_ctl->private_value >> 16;
+ if (snd_hdac_regmap_add_vendor_verb(&codec->core, wr_verb))
+ return -ENOMEM;
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, spec->aloopback_ctl))
+ return -ENOMEM;
+ }
+
+ if (spec->have_spdif_mux) {
+ err = stac_create_spdif_mux_ctls(codec);
+ if (err < 0)
+ return err;
+ }
+
+ stac_init_power_map(codec);
+
+ return 0;
+}
+
+static int stac_init(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+ int i;
+
+ /* override some hints */
+ stac_store_hints(codec);
+
+ /* set up GPIO */
+ /* turn on EAPD statically when spec->eapd_switch isn't set.
+ * otherwise, unsol event will turn it on/off dynamically
+ */
+ if (!spec->eapd_switch)
+ spec->gpio_data |= spec->eapd_mask;
+ stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
+
+ snd_hda_gen_init(codec);
+
+ /* sync the power-map */
+ if (spec->num_pwrs)
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_IDT_SET_POWER_MAP,
+ spec->power_map_bits);
+
+ /* power down inactive ADCs */
+ if (spec->powerdown_adcs) {
+ for (i = 0; i < spec->gen.num_all_adcs; i++) {
+ if (spec->active_adcs & (1 << i))
+ continue;
+ snd_hda_codec_write(codec, spec->gen.all_adcs[i], 0,
+ AC_VERB_SET_POWER_STATE,
+ AC_PWRST_D3);
+ }
+ }
+
+ return 0;
+}
+
+static void stac_shutup(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ snd_hda_shutup_pins(codec);
+
+ if (spec->eapd_mask)
+ stac_gpio_set(codec, spec->gpio_mask,
+ spec->gpio_dir, spec->gpio_data &
+ ~spec->eapd_mask);
+}
+
+#define stac_free snd_hda_gen_free
+
+#ifdef CONFIG_SND_PROC_FS
+static void stac92hd_proc_hook(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ if (nid == codec->core.afg)
+ snd_iprintf(buffer, "Power-Map: 0x%02x\n",
+ snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_IDT_GET_POWER_MAP, 0));
+}
+
+static void analog_loop_proc_hook(struct snd_info_buffer *buffer,
+ struct hda_codec *codec,
+ unsigned int verb)
+{
+ snd_iprintf(buffer, "Analog Loopback: 0x%02x\n",
+ snd_hda_codec_read(codec, codec->core.afg, 0, verb, 0));
+}
+
+/* stac92hd71bxx, stac92hd73xx */
+static void stac92hd7x_proc_hook(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ stac92hd_proc_hook(buffer, codec, nid);
+ if (nid == codec->core.afg)
+ analog_loop_proc_hook(buffer, codec, 0xfa0);
+}
+
+static void stac9205_proc_hook(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ if (nid == codec->core.afg)
+ analog_loop_proc_hook(buffer, codec, 0xfe0);
+}
+
+static void stac927x_proc_hook(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ if (nid == codec->core.afg)
+ analog_loop_proc_hook(buffer, codec, 0xfeb);
+}
+#else
+#define stac92hd_proc_hook NULL
+#define stac92hd7x_proc_hook NULL
+#define stac9205_proc_hook NULL
+#define stac927x_proc_hook NULL
+#endif
+
+#ifdef CONFIG_PM
+static int stac_suspend(struct hda_codec *codec)
+{
+ stac_shutup(codec);
+ return 0;
+}
+#else
+#define stac_suspend NULL
+#endif /* CONFIG_PM */
+
+static const struct hda_codec_ops stac_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = stac_init,
+ .free = stac_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .suspend = stac_suspend,
+#endif
+ .reboot_notify = stac_shutup,
+};
+
+static int alloc_stac_spec(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ snd_hda_gen_spec_init(&spec->gen);
+ codec->spec = spec;
+ codec->no_trigger_sense = 1; /* seems common with STAC/IDT codecs */
+ spec->gen.dac_min_mute = true;
+ codec->patch_ops = stac_patch_ops;
+ return 0;
+}
+
+static int patch_stac9200(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->linear_tone_beep = 1;
+ spec->gen.own_eapd_ctl = 1;
+
+ codec->power_filter = snd_hda_codec_eapd_power_filter;
+
+ snd_hda_add_verbs(codec, stac9200_eapd_init);
+
+ snd_hda_pick_fixup(codec, stac9200_models, stac9200_fixup_tbl,
+ stac9200_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static int patch_stac925x(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->linear_tone_beep = 1;
+ spec->gen.own_eapd_ctl = 1;
+
+ snd_hda_add_verbs(codec, stac925x_core_init);
+
+ snd_hda_pick_fixup(codec, stac925x_models, stac925x_fixup_tbl,
+ stac925x_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static int patch_stac92hd73xx(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+ int num_dacs;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ /* enable power_save_node only for new 92HD89xx chips, as it causes
+ * click noises on old 92HD73xx chips.
+ */
+ if ((codec->core.vendor_id & 0xfffffff0) != 0x111d7670)
+ codec->power_save_node = 1;
+ spec->linear_tone_beep = 0;
+ spec->gen.mixer_nid = 0x1d;
+ spec->have_spdif_mux = 1;
+
+ num_dacs = snd_hda_get_num_conns(codec, 0x0a) - 1;
+ if (num_dacs < 3 || num_dacs > 5) {
+ codec_warn(codec,
+ "Could not determine number of channels defaulting to DAC count\n");
+ num_dacs = 5;
+ }
+
+ switch (num_dacs) {
+ case 0x3: /* 6 Channel */
+ spec->aloopback_ctl = &stac92hd73xx_6ch_loopback;
+ break;
+ case 0x4: /* 8 Channel */
+ spec->aloopback_ctl = &stac92hd73xx_8ch_loopback;
+ break;
+ case 0x5: /* 10 Channel */
+ spec->aloopback_ctl = &stac92hd73xx_10ch_loopback;
+ break;
+ }
+
+ spec->aloopback_mask = 0x01;
+ spec->aloopback_shift = 8;
+
+ spec->gen.beep_nid = 0x1c; /* digital beep */
+
+ /* GPIO0 High = Enable EAPD */
+ spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1;
+ spec->gpio_data = 0x01;
+
+ spec->eapd_switch = 1;
+
+ spec->num_pwrs = ARRAY_SIZE(stac92hd73xx_pwr_nids);
+ spec->pwr_nids = stac92hd73xx_pwr_nids;
+
+ spec->gen.own_eapd_ctl = 1;
+ spec->gen.power_down_unused = 1;
+
+ snd_hda_pick_fixup(codec, stac92hd73xx_models, stac92hd73xx_fixup_tbl,
+ stac92hd73xx_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ if (!spec->volknob_init)
+ snd_hda_add_verbs(codec, stac92hd73xx_core_init);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ /* Don't GPIO-mute speakers if there are no internal speakers, because
+ * the GPIO might be necessary for Headphone
+ */
+ if (spec->eapd_switch && !has_builtin_speaker(codec))
+ spec->eapd_switch = 0;
+
+ codec->proc_widget_hook = stac92hd7x_proc_hook;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static void stac_setup_gpio(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec = codec->spec;
+
+ spec->gpio_mask |= spec->eapd_mask;
+ if (spec->gpio_led) {
+ if (!spec->vref_mute_led_nid) {
+ spec->gpio_mask |= spec->gpio_led;
+ spec->gpio_dir |= spec->gpio_led;
+ spec->gpio_data |= spec->gpio_led;
+ } else {
+ codec->power_filter = stac_vref_led_power_filter;
+ }
+ }
+
+ if (spec->mic_mute_led_gpio) {
+ spec->gpio_mask |= spec->mic_mute_led_gpio;
+ spec->gpio_dir |= spec->mic_mute_led_gpio;
+ spec->mic_enabled = 0;
+ spec->gpio_data |= spec->mic_mute_led_gpio;
+ snd_hda_gen_add_micmute_led_cdev(codec, stac_capture_led_update);
+ }
+}
+
+static int patch_stac92hd83xxx(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ /* longer delay needed for D3 */
+ codec->core.power_caps &= ~AC_PWRST_EPSS;
+
+ spec = codec->spec;
+ codec->power_save_node = 1;
+ spec->linear_tone_beep = 0;
+ spec->gen.own_eapd_ctl = 1;
+ spec->gen.power_down_unused = 1;
+ spec->gen.mixer_nid = 0x1b;
+
+ spec->gen.beep_nid = 0x21; /* digital beep */
+ spec->pwr_nids = stac92hd83xxx_pwr_nids;
+ spec->num_pwrs = ARRAY_SIZE(stac92hd83xxx_pwr_nids);
+ spec->default_polarity = -1; /* no default cfg */
+
+ snd_hda_add_verbs(codec, stac92hd83xxx_core_init);
+
+ snd_hda_pick_fixup(codec, stac92hd83xxx_models, stac92hd83xxx_fixup_tbl,
+ stac92hd83xxx_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ stac_setup_gpio(codec);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ codec->proc_widget_hook = stac92hd_proc_hook;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static const hda_nid_t stac92hd95_pwr_nids[] = {
+ 0x0a, 0x0b, 0x0c, 0x0d
+};
+
+static int patch_stac92hd95(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ /* longer delay needed for D3 */
+ codec->core.power_caps &= ~AC_PWRST_EPSS;
+
+ spec = codec->spec;
+ codec->power_save_node = 1;
+ spec->linear_tone_beep = 0;
+ spec->gen.own_eapd_ctl = 1;
+ spec->gen.power_down_unused = 1;
+
+ spec->gen.beep_nid = 0x19; /* digital beep */
+ spec->pwr_nids = stac92hd95_pwr_nids;
+ spec->num_pwrs = ARRAY_SIZE(stac92hd95_pwr_nids);
+ spec->default_polarity = 0;
+
+ snd_hda_pick_fixup(codec, stac92hd95_models, stac92hd95_fixup_tbl,
+ stac92hd95_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ stac_setup_gpio(codec);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ codec->proc_widget_hook = stac92hd_proc_hook;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static int patch_stac92hd71bxx(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ const hda_nid_t *unmute_nids = stac92hd71bxx_unmute_nids;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ /* disabled power_save_node since it causes noises on a Dell machine */
+ /* codec->power_save_node = 1; */
+ spec->linear_tone_beep = 0;
+ spec->gen.own_eapd_ctl = 1;
+ spec->gen.power_down_unused = 1;
+ spec->gen.mixer_nid = 0x17;
+ spec->have_spdif_mux = 1;
+
+ /* GPIO0 = EAPD */
+ spec->gpio_mask = 0x01;
+ spec->gpio_dir = 0x01;
+ spec->gpio_data = 0x01;
+
+ switch (codec->core.vendor_id) {
+ case 0x111d76b6: /* 4 Port without Analog Mixer */
+ case 0x111d76b7:
+ unmute_nids++;
+ break;
+ case 0x111d7608: /* 5 Port with Analog Mixer */
+ if ((codec->core.revision_id & 0xf) == 0 ||
+ (codec->core.revision_id & 0xf) == 1)
+ spec->stream_delay = 40; /* 40 milliseconds */
+
+ /* disable VSW */
+ unmute_nids++;
+ snd_hda_codec_set_pincfg(codec, 0x0f, 0x40f000f0);
+ snd_hda_codec_set_pincfg(codec, 0x19, 0x40f000f3);
+ break;
+ case 0x111d7603: /* 6 Port with Analog Mixer */
+ if ((codec->core.revision_id & 0xf) == 1)
+ spec->stream_delay = 40; /* 40 milliseconds */
+
+ break;
+ }
+
+ if (get_wcaps_type(get_wcaps(codec, 0x28)) == AC_WID_VOL_KNB)
+ snd_hda_add_verbs(codec, stac92hd71bxx_core_init);
+
+ if (get_wcaps(codec, 0xa) & AC_WCAP_IN_AMP) {
+ const hda_nid_t *p;
+ for (p = unmute_nids; *p; p++)
+ snd_hda_codec_amp_init_stereo(codec, *p, HDA_INPUT, 0,
+ 0xff, 0x00);
+ }
+
+ spec->aloopback_ctl = &stac92hd71bxx_loopback;
+ spec->aloopback_mask = 0x50;
+ spec->aloopback_shift = 0;
+
+ spec->powerdown_adcs = 1;
+ spec->gen.beep_nid = 0x26; /* digital beep */
+ spec->num_pwrs = ARRAY_SIZE(stac92hd71bxx_pwr_nids);
+ spec->pwr_nids = stac92hd71bxx_pwr_nids;
+
+ snd_hda_pick_fixup(codec, stac92hd71bxx_models, stac92hd71bxx_fixup_tbl,
+ stac92hd71bxx_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ stac_setup_gpio(codec);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ codec->proc_widget_hook = stac92hd7x_proc_hook;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static int patch_stac922x(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->linear_tone_beep = 1;
+ spec->gen.own_eapd_ctl = 1;
+
+ snd_hda_add_verbs(codec, stac922x_core_init);
+
+ /* Fix Mux capture level; max to 2 */
+ snd_hda_override_amp_caps(codec, 0x12, HDA_OUTPUT,
+ (0 << AC_AMPCAP_OFFSET_SHIFT) |
+ (2 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+
+ snd_hda_pick_fixup(codec, stac922x_models, stac922x_fixup_tbl,
+ stac922x_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static const char * const stac927x_spdif_labels[] = {
+ "Digital Playback", "ADAT", "Analog Mux 1",
+ "Analog Mux 2", "Analog Mux 3", NULL
+};
+
+static int patch_stac927x(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->linear_tone_beep = 1;
+ spec->gen.own_eapd_ctl = 1;
+ spec->have_spdif_mux = 1;
+ spec->spdif_labels = stac927x_spdif_labels;
+
+ spec->gen.beep_nid = 0x23; /* digital beep */
+
+ /* GPIO0 High = Enable EAPD */
+ spec->eapd_mask = spec->gpio_mask = 0x01;
+ spec->gpio_dir = spec->gpio_data = 0x01;
+
+ spec->aloopback_ctl = &stac927x_loopback;
+ spec->aloopback_mask = 0x40;
+ spec->aloopback_shift = 0;
+ spec->eapd_switch = 1;
+
+ snd_hda_pick_fixup(codec, stac927x_models, stac927x_fixup_tbl,
+ stac927x_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ if (!spec->volknob_init)
+ snd_hda_add_verbs(codec, stac927x_core_init);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ codec->proc_widget_hook = stac927x_proc_hook;
+
+ /*
+ * !!FIXME!!
+ * The STAC927x seem to require fairly long delays for certain
+ * command sequences. With too short delays (even if the answer
+ * is set to RIRB properly), it results in the silence output
+ * on some hardwares like Dell.
+ *
+ * The below flag enables the longer delay (see get_response
+ * in hda_intel.c).
+ */
+ codec->bus->core.needs_damn_long_delay = 1;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+static int patch_stac9205(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->linear_tone_beep = 1;
+ spec->gen.own_eapd_ctl = 1;
+ spec->have_spdif_mux = 1;
+
+ spec->gen.beep_nid = 0x23; /* digital beep */
+
+ snd_hda_add_verbs(codec, stac9205_core_init);
+ spec->aloopback_ctl = &stac9205_loopback;
+
+ spec->aloopback_mask = 0x40;
+ spec->aloopback_shift = 0;
+
+ /* GPIO0 High = EAPD */
+ spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1;
+ spec->gpio_data = 0x01;
+
+ /* Turn on/off EAPD per HP plugging */
+ spec->eapd_switch = 1;
+
+ snd_hda_pick_fixup(codec, stac9205_models, stac9205_fixup_tbl,
+ stac9205_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return err;
+ }
+
+ codec->proc_widget_hook = stac9205_proc_hook;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+/*
+ * STAC9872 hack
+ */
+
+static const struct hda_verb stac9872_core_init[] = {
+ {0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */
+ {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */
+ {}
+};
+
+static const struct hda_pintbl stac9872_vaio_pin_configs[] = {
+ { 0x0a, 0x03211020 },
+ { 0x0b, 0x411111f0 },
+ { 0x0c, 0x411111f0 },
+ { 0x0d, 0x03a15030 },
+ { 0x0e, 0x411111f0 },
+ { 0x0f, 0x90170110 },
+ { 0x11, 0x411111f0 },
+ { 0x13, 0x411111f0 },
+ { 0x14, 0x90a7013e },
+ {}
+};
+
+static const struct hda_model_fixup stac9872_models[] = {
+ { .id = STAC_9872_VAIO, .name = "vaio" },
+ {}
+};
+
+static const struct hda_fixup stac9872_fixups[] = {
+ [STAC_9872_VAIO] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = stac9872_vaio_pin_configs,
+ },
+};
+
+static const struct snd_pci_quirk stac9872_fixup_tbl[] = {
+ SND_PCI_QUIRK_MASK(0x104d, 0xfff0, 0x81e0,
+ "Sony VAIO F/S", STAC_9872_VAIO),
+ {} /* terminator */
+};
+
+static int patch_stac9872(struct hda_codec *codec)
+{
+ struct sigmatel_spec *spec;
+ int err;
+
+ err = alloc_stac_spec(codec);
+ if (err < 0)
+ return err;
+
+ spec = codec->spec;
+ spec->linear_tone_beep = 1;
+ spec->gen.own_eapd_ctl = 1;
+
+ snd_hda_add_verbs(codec, stac9872_core_init);
+
+ snd_hda_pick_fixup(codec, stac9872_models, stac9872_fixup_tbl,
+ stac9872_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = stac_parse_auto_config(codec);
+ if (err < 0) {
+ stac_free(codec);
+ return -EINVAL;
+ }
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return 0;
+}
+
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_sigmatel[] = {
+ HDA_CODEC_ENTRY(0x83847690, "STAC9200", patch_stac9200),
+ HDA_CODEC_ENTRY(0x83847882, "STAC9220 A1", patch_stac922x),
+ HDA_CODEC_ENTRY(0x83847680, "STAC9221 A1", patch_stac922x),
+ HDA_CODEC_ENTRY(0x83847880, "STAC9220 A2", patch_stac922x),
+ HDA_CODEC_ENTRY(0x83847681, "STAC9220D/9223D A2", patch_stac922x),
+ HDA_CODEC_ENTRY(0x83847682, "STAC9221 A2", patch_stac922x),
+ HDA_CODEC_ENTRY(0x83847683, "STAC9221D A2", patch_stac922x),
+ HDA_CODEC_ENTRY(0x83847618, "STAC9227", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847619, "STAC9227", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847638, "STAC92HD700", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847616, "STAC9228", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847617, "STAC9228", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847614, "STAC9229", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847615, "STAC9229", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847620, "STAC9274", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847621, "STAC9274D", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847622, "STAC9273X", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847623, "STAC9273D", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847624, "STAC9272X", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847625, "STAC9272D", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847626, "STAC9271X", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847627, "STAC9271D", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847628, "STAC9274X5NH", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847629, "STAC9274D5NH", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847632, "STAC9202", patch_stac925x),
+ HDA_CODEC_ENTRY(0x83847633, "STAC9202D", patch_stac925x),
+ HDA_CODEC_ENTRY(0x83847634, "STAC9250", patch_stac925x),
+ HDA_CODEC_ENTRY(0x83847635, "STAC9250D", patch_stac925x),
+ HDA_CODEC_ENTRY(0x83847636, "STAC9251", patch_stac925x),
+ HDA_CODEC_ENTRY(0x83847637, "STAC9250D", patch_stac925x),
+ HDA_CODEC_ENTRY(0x83847645, "92HD206X", patch_stac927x),
+ HDA_CODEC_ENTRY(0x83847646, "92HD206D", patch_stac927x),
+ /* The following does not take into account .id=0x83847661 when subsys =
+ * 104D0C00 which is STAC9225s. Because of this, some SZ Notebooks are
+ * currently not fully supported.
+ */
+ HDA_CODEC_ENTRY(0x83847661, "CXD9872RD/K", patch_stac9872),
+ HDA_CODEC_ENTRY(0x83847662, "STAC9872AK", patch_stac9872),
+ HDA_CODEC_ENTRY(0x83847664, "CXD9872AKD", patch_stac9872),
+ HDA_CODEC_ENTRY(0x83847698, "STAC9205", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a0, "STAC9205", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a1, "STAC9205D", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a2, "STAC9204", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a3, "STAC9204D", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a4, "STAC9255", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a5, "STAC9255D", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a6, "STAC9254", patch_stac9205),
+ HDA_CODEC_ENTRY(0x838476a7, "STAC9254D", patch_stac9205),
+ HDA_CODEC_ENTRY(0x111d7603, "92HD75B3X5", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d7604, "92HD83C1X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76d4, "92HD83C1C5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d7605, "92HD81B1X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76d5, "92HD81B1C5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76d1, "92HD87B1/3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76d9, "92HD87B2/4", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d7666, "92HD88B3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d7667, "92HD88B1", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d7668, "92HD88B2", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d7669, "92HD88B4", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d7608, "92HD75B2X5", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d7674, "92HD73D1X5", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d7675, "92HD73C1X5", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d7676, "92HD73E1X5", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d7695, "92HD95", patch_stac92hd95),
+ HDA_CODEC_ENTRY(0x111d76b0, "92HD71B8X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b1, "92HD71B8X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b2, "92HD71B7X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b3, "92HD71B7X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b4, "92HD71B6X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b5, "92HD71B6X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b6, "92HD71B5X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76b7, "92HD71B5X", patch_stac92hd71bxx),
+ HDA_CODEC_ENTRY(0x111d76c0, "92HD89C3", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c1, "92HD89C2", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c2, "92HD89C1", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c3, "92HD89B3", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c4, "92HD89B2", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c5, "92HD89B1", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c6, "92HD89E3", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c7, "92HD89E2", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c8, "92HD89E1", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76c9, "92HD89D3", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76ca, "92HD89D2", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76cb, "92HD89D1", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76cc, "92HD89F3", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76cd, "92HD89F2", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76ce, "92HD89F1", patch_stac92hd73xx),
+ HDA_CODEC_ENTRY(0x111d76df, "92HD93BXX", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76e0, "92HD91BXX", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76e3, "92HD98BXX", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76e5, "92HD99BXX", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76e7, "92HD90BXX", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76e8, "92HD66B1X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76e9, "92HD66B2X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76ea, "92HD66B3X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76eb, "92HD66C1X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76ec, "92HD66C2X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76ed, "92HD66C3X5", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76ee, "92HD66B1X3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76ef, "92HD66B2X3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76f0, "92HD66B3X3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76f1, "92HD66C1X3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76f2, "92HD66C2X3", patch_stac92hd83xxx),
+ HDA_CODEC_ENTRY(0x111d76f3, "92HD66C3/65", patch_stac92hd83xxx),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_sigmatel);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IDT/Sigmatel HD-audio codec");
+
+static struct hda_codec_driver sigmatel_driver = {
+ .id = snd_hda_id_sigmatel,
+};
+
+module_hda_codec_driver(sigmatel_driver);
diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c
new file mode 100644
index 000000000..29abc96dc
--- /dev/null
+++ b/sound/pci/hda/patch_via.c
@@ -0,0 +1,1255 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for VIA VT17xx/VT18xx/VT20xx codec
+ *
+ * (C) 2006-2009 VIA Technology, Inc.
+ * (C) 2006-2008 Takashi Iwai <tiwai@suse.de>
+ */
+
+/* * * * * * * * * * * * * * Release History * * * * * * * * * * * * * * * * */
+/* */
+/* 2006-03-03 Lydia Wang Create the basic patch to support VT1708 codec */
+/* 2006-03-14 Lydia Wang Modify hard code for some pin widget nid */
+/* 2006-08-02 Lydia Wang Add support to VT1709 codec */
+/* 2006-09-08 Lydia Wang Fix internal loopback recording source select bug */
+/* 2007-09-12 Lydia Wang Add EAPD enable during driver initialization */
+/* 2007-09-17 Lydia Wang Add VT1708B codec support */
+/* 2007-11-14 Lydia Wang Add VT1708A codec HP and CD pin connect config */
+/* 2008-02-03 Lydia Wang Fix Rear channels and Back channels inverse issue */
+/* 2008-03-06 Lydia Wang Add VT1702 codec and VT1708S codec support */
+/* 2008-04-09 Lydia Wang Add mute front speaker when HP plugin */
+/* 2008-04-09 Lydia Wang Add Independent HP feature */
+/* 2008-05-28 Lydia Wang Add second S/PDIF Out support for VT1702 */
+/* 2008-09-15 Logan Li Add VT1708S Mic Boost workaround/backdoor */
+/* 2009-02-16 Logan Li Add support for VT1718S */
+/* 2009-03-13 Logan Li Add support for VT1716S */
+/* 2009-04-14 Lydai Wang Add support for VT1828S and VT2020 */
+/* 2009-07-08 Lydia Wang Add support for VT2002P */
+/* 2009-07-21 Lydia Wang Add support for VT1812 */
+/* 2009-09-19 Lydia Wang Add support for VT1818S */
+/* */
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/asoundef.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+/* Pin Widget NID */
+#define VT1708_HP_PIN_NID 0x20
+#define VT1708_CD_PIN_NID 0x24
+
+enum VIA_HDA_CODEC {
+ UNKNOWN = -1,
+ VT1708,
+ VT1709_10CH,
+ VT1709_6CH,
+ VT1708B_8CH,
+ VT1708B_4CH,
+ VT1708S,
+ VT1708BCE,
+ VT1702,
+ VT1718S,
+ VT1716S,
+ VT2002P,
+ VT1812,
+ VT1802,
+ VT1705CF,
+ VT1808,
+ CODEC_TYPES,
+};
+
+#define VT2002P_COMPATIBLE(spec) \
+ ((spec)->codec_type == VT2002P ||\
+ (spec)->codec_type == VT1812 ||\
+ (spec)->codec_type == VT1802)
+
+struct via_spec {
+ struct hda_gen_spec gen;
+
+ /* HP mode source */
+ unsigned int dmic_enabled;
+ enum VIA_HDA_CODEC codec_type;
+
+ /* analog low-power control */
+ bool alc_mode;
+
+ /* work to check hp jack state */
+ int hp_work_active;
+ int vt1708_jack_detect;
+};
+
+static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec);
+static void via_playback_pcm_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action);
+
+static const struct hda_codec_ops via_patch_ops; /* defined below */
+
+static struct via_spec *via_new_spec(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return NULL;
+
+ codec->spec = spec;
+ snd_hda_gen_spec_init(&spec->gen);
+ spec->codec_type = get_codec_type(codec);
+ /* VT1708BCE & VT1708S are almost same */
+ if (spec->codec_type == VT1708BCE)
+ spec->codec_type = VT1708S;
+ spec->gen.indep_hp = 1;
+ spec->gen.keep_eapd_on = 1;
+ spec->gen.dac_min_mute = 1;
+ spec->gen.pcm_playback_hook = via_playback_pcm_hook;
+ spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO;
+ codec->power_save_node = 1;
+ spec->gen.power_down_unused = 1;
+ codec->patch_ops = via_patch_ops;
+ return spec;
+}
+
+static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec)
+{
+ u32 vendor_id = codec->core.vendor_id;
+ u16 ven_id = vendor_id >> 16;
+ u16 dev_id = vendor_id & 0xffff;
+ enum VIA_HDA_CODEC codec_type;
+
+ /* get codec type */
+ if (ven_id != 0x1106)
+ codec_type = UNKNOWN;
+ else if (dev_id >= 0x1708 && dev_id <= 0x170b)
+ codec_type = VT1708;
+ else if (dev_id >= 0xe710 && dev_id <= 0xe713)
+ codec_type = VT1709_10CH;
+ else if (dev_id >= 0xe714 && dev_id <= 0xe717)
+ codec_type = VT1709_6CH;
+ else if (dev_id >= 0xe720 && dev_id <= 0xe723) {
+ codec_type = VT1708B_8CH;
+ if (snd_hda_param_read(codec, 0x16, AC_PAR_CONNLIST_LEN) == 0x7)
+ codec_type = VT1708BCE;
+ } else if (dev_id >= 0xe724 && dev_id <= 0xe727)
+ codec_type = VT1708B_4CH;
+ else if ((dev_id & 0xfff) == 0x397
+ && (dev_id >> 12) < 8)
+ codec_type = VT1708S;
+ else if ((dev_id & 0xfff) == 0x398
+ && (dev_id >> 12) < 8)
+ codec_type = VT1702;
+ else if ((dev_id & 0xfff) == 0x428
+ && (dev_id >> 12) < 8)
+ codec_type = VT1718S;
+ else if (dev_id == 0x0433 || dev_id == 0xa721)
+ codec_type = VT1716S;
+ else if (dev_id == 0x0441 || dev_id == 0x4441)
+ codec_type = VT1718S;
+ else if (dev_id == 0x0438 || dev_id == 0x4438)
+ codec_type = VT2002P;
+ else if (dev_id == 0x0448)
+ codec_type = VT1812;
+ else if (dev_id == 0x0440)
+ codec_type = VT1708S;
+ else if ((dev_id & 0xfff) == 0x446)
+ codec_type = VT1802;
+ else if (dev_id == 0x4760)
+ codec_type = VT1705CF;
+ else if (dev_id == 0x4761 || dev_id == 0x4762)
+ codec_type = VT1808;
+ else
+ codec_type = UNKNOWN;
+ return codec_type;
+};
+
+static void analog_low_current_mode(struct hda_codec *codec);
+static bool is_aa_path_mute(struct hda_codec *codec);
+
+#define hp_detect_with_aa(codec) \
+ (snd_hda_get_bool_hint(codec, "analog_loopback_hp_detect") == 1 && \
+ !is_aa_path_mute(codec))
+
+static void vt1708_stop_hp_work(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ if (spec->codec_type != VT1708 || !spec->gen.autocfg.hp_outs)
+ return;
+ if (spec->hp_work_active) {
+ snd_hda_codec_write(codec, 0x1, 0, 0xf81, 1);
+ codec->jackpoll_interval = 0;
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ spec->hp_work_active = false;
+ }
+}
+
+static void vt1708_update_hp_work(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ if (spec->codec_type != VT1708 || !spec->gen.autocfg.hp_outs)
+ return;
+ if (spec->vt1708_jack_detect) {
+ if (!spec->hp_work_active) {
+ codec->jackpoll_interval = msecs_to_jiffies(100);
+ snd_hda_codec_write(codec, 0x1, 0, 0xf81, 0);
+ schedule_delayed_work(&codec->jackpoll_work, 0);
+ spec->hp_work_active = true;
+ }
+ } else if (!hp_detect_with_aa(codec))
+ vt1708_stop_hp_work(codec);
+}
+
+static int via_pin_power_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
+}
+
+static int via_pin_power_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct via_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->gen.power_down_unused;
+ return 0;
+}
+
+static int via_pin_power_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct via_spec *spec = codec->spec;
+ bool val = !!ucontrol->value.enumerated.item[0];
+
+ if (val == spec->gen.power_down_unused)
+ return 0;
+ /* codec->power_save_node = val; */ /* widget PM seems yet broken */
+ spec->gen.power_down_unused = val;
+ analog_low_current_mode(codec);
+ return 1;
+}
+
+static const struct snd_kcontrol_new via_pin_power_ctl_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Dynamic Power-Control",
+ .info = via_pin_power_ctl_info,
+ .get = via_pin_power_ctl_get,
+ .put = via_pin_power_ctl_put,
+};
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* additional beep mixers; the actual parameters are overwritten at build */
+static const struct snd_kcontrol_new via_beep_mixer[] = {
+ HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT),
+};
+
+static int set_beep_amp(struct via_spec *spec, hda_nid_t nid,
+ int idx, int dir)
+{
+ struct snd_kcontrol_new *knew;
+ unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir);
+ int i;
+
+ spec->gen.beep_nid = nid;
+ for (i = 0; i < ARRAY_SIZE(via_beep_mixer); i++) {
+ knew = snd_hda_gen_add_kctl(&spec->gen, NULL,
+ &via_beep_mixer[i]);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = beep_amp;
+ }
+ return 0;
+}
+
+static int auto_parse_beep(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec)
+ if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP)
+ return set_beep_amp(spec, nid, 0, HDA_OUTPUT);
+ return 0;
+}
+#else
+#define auto_parse_beep(codec) 0
+#endif
+
+/* check AA path's mute status */
+static bool is_aa_path_mute(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ const struct hda_amp_list *p;
+ int ch, v;
+
+ p = spec->gen.loopback.amplist;
+ if (!p)
+ return true;
+ for (; p->nid; p++) {
+ for (ch = 0; ch < 2; ch++) {
+ v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir,
+ p->idx);
+ if (!(v & HDA_AMP_MUTE) && v > 0)
+ return false;
+ }
+ }
+ return true;
+}
+
+/* enter/exit analog low-current mode */
+static void __analog_low_current_mode(struct hda_codec *codec, bool force)
+{
+ struct via_spec *spec = codec->spec;
+ bool enable;
+ unsigned int verb, parm;
+
+ if (!codec->power_save_node)
+ enable = false;
+ else
+ enable = is_aa_path_mute(codec) && !spec->gen.active_streams;
+ if (enable == spec->alc_mode && !force)
+ return;
+ spec->alc_mode = enable;
+
+ /* decide low current mode's verb & parameter */
+ switch (spec->codec_type) {
+ case VT1708B_8CH:
+ case VT1708B_4CH:
+ verb = 0xf70;
+ parm = enable ? 0x02 : 0x00; /* 0x02: 2/3x, 0x00: 1x */
+ break;
+ case VT1708S:
+ case VT1718S:
+ case VT1716S:
+ verb = 0xf73;
+ parm = enable ? 0x51 : 0xe1; /* 0x51: 4/28x, 0xe1: 1x */
+ break;
+ case VT1702:
+ verb = 0xf73;
+ parm = enable ? 0x01 : 0x1d; /* 0x01: 4/40x, 0x1d: 1x */
+ break;
+ case VT2002P:
+ case VT1812:
+ case VT1802:
+ verb = 0xf93;
+ parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */
+ break;
+ case VT1705CF:
+ case VT1808:
+ verb = 0xf82;
+ parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */
+ break;
+ default:
+ return; /* other codecs are not supported */
+ }
+ /* send verb */
+ snd_hda_codec_write(codec, codec->core.afg, 0, verb, parm);
+}
+
+static void analog_low_current_mode(struct hda_codec *codec)
+{
+ return __analog_low_current_mode(codec, false);
+}
+
+static void via_playback_pcm_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ analog_low_current_mode(codec);
+ vt1708_update_hp_work(codec);
+}
+
+static void via_free(struct hda_codec *codec)
+{
+ vt1708_stop_hp_work(codec);
+ snd_hda_gen_free(codec);
+}
+
+#ifdef CONFIG_PM
+static int via_suspend(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ vt1708_stop_hp_work(codec);
+
+ /* Fix pop noise on headphones */
+ if (spec->codec_type == VT1802)
+ snd_hda_shutup_pins(codec);
+
+ return 0;
+}
+
+static int via_resume(struct hda_codec *codec)
+{
+ /* some delay here to make jack detection working (bko#98921) */
+ msleep(10);
+ codec->patch_ops.init(codec);
+ snd_hda_regmap_sync(codec);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int via_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct via_spec *spec = codec->spec;
+ analog_low_current_mode(codec);
+ vt1708_update_hp_work(codec);
+ return snd_hda_check_amp_list_power(codec, &spec->gen.loopback, nid);
+}
+#endif
+
+/*
+ */
+
+static int via_init(struct hda_codec *codec);
+
+static const struct hda_codec_ops via_patch_ops = {
+ .build_controls = snd_hda_gen_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = via_init,
+ .free = via_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .suspend = via_suspend,
+ .resume = via_resume,
+ .check_power_status = via_check_power_status,
+#endif
+};
+
+
+static const struct hda_verb vt1708_init_verbs[] = {
+ /* power down jack detect function */
+ {0x1, 0xf81, 0x1},
+ { }
+};
+static void vt1708_set_pinconfig_connect(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int def_conf;
+ unsigned char seqassoc;
+
+ def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ seqassoc = (unsigned char) get_defcfg_association(def_conf);
+ seqassoc = (seqassoc << 4) | get_defcfg_sequence(def_conf);
+ if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE
+ && (seqassoc == 0xf0 || seqassoc == 0xff)) {
+ def_conf = def_conf & (~(AC_JACK_PORT_BOTH << 30));
+ snd_hda_codec_set_pincfg(codec, nid, def_conf);
+ }
+
+ return;
+}
+
+static int vt1708_jack_detect_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct via_spec *spec = codec->spec;
+
+ if (spec->codec_type != VT1708)
+ return 0;
+ ucontrol->value.integer.value[0] = spec->vt1708_jack_detect;
+ return 0;
+}
+
+static int vt1708_jack_detect_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct via_spec *spec = codec->spec;
+ int val;
+
+ if (spec->codec_type != VT1708)
+ return 0;
+ val = !!ucontrol->value.integer.value[0];
+ if (spec->vt1708_jack_detect == val)
+ return 0;
+ spec->vt1708_jack_detect = val;
+ vt1708_update_hp_work(codec);
+ return 1;
+}
+
+static const struct snd_kcontrol_new vt1708_jack_detect_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Jack Detect",
+ .count = 1,
+ .info = snd_ctl_boolean_mono_info,
+ .get = vt1708_jack_detect_get,
+ .put = vt1708_jack_detect_put,
+};
+
+static const struct badness_table via_main_out_badness = {
+ .no_primary_dac = 0x10000,
+ .no_dac = 0x4000,
+ .shared_primary = 0x10000,
+ .shared_surr = 0x20,
+ .shared_clfe = 0x20,
+ .shared_surr_main = 0x20,
+};
+static const struct badness_table via_extra_out_badness = {
+ .no_primary_dac = 0x4000,
+ .no_dac = 0x4000,
+ .shared_primary = 0x12,
+ .shared_surr = 0x20,
+ .shared_clfe = 0x20,
+ .shared_surr_main = 0x10,
+};
+
+static int via_parse_auto_config(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ int err;
+
+ spec->gen.main_out_badness = &via_main_out_badness;
+ spec->gen.extra_out_badness = &via_extra_out_badness;
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0);
+ if (err < 0)
+ return err;
+
+ err = auto_parse_beep(codec);
+ if (err < 0)
+ return err;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
+ if (err < 0)
+ return err;
+
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &via_pin_power_ctl_enum))
+ return -ENOMEM;
+
+ /* disable widget PM at start for compatibility */
+ codec->power_save_node = 0;
+ spec->gen.power_down_unused = 0;
+ return 0;
+}
+
+static int via_init(struct hda_codec *codec)
+{
+ /* init power states */
+ __analog_low_current_mode(codec, true);
+
+ snd_hda_gen_init(codec);
+
+ vt1708_update_hp_work(codec);
+
+ return 0;
+}
+
+static int vt1708_build_controls(struct hda_codec *codec)
+{
+ /* In order not to create "Phantom Jack" controls,
+ temporary enable jackpoll */
+ int err;
+ int old_interval = codec->jackpoll_interval;
+ codec->jackpoll_interval = msecs_to_jiffies(100);
+ err = snd_hda_gen_build_controls(codec);
+ codec->jackpoll_interval = old_interval;
+ return err;
+}
+
+static int vt1708_build_pcms(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ int i, err;
+
+ err = snd_hda_gen_build_pcms(codec);
+ if (err < 0 || codec->core.vendor_id != 0x11061708)
+ return err;
+
+ /* We got noisy outputs on the right channel on VT1708 when
+ * 24bit samples are used. Until any workaround is found,
+ * disable the 24bit format, so far.
+ */
+ for (i = 0; i < ARRAY_SIZE(spec->gen.pcm_rec); i++) {
+ struct hda_pcm *info = spec->gen.pcm_rec[i];
+ if (!info)
+ continue;
+ if (!info->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams ||
+ info->pcm_type != HDA_PCM_TYPE_AUDIO)
+ continue;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].formats =
+ SNDRV_PCM_FMTBIT_S16_LE;
+ }
+
+ return 0;
+}
+
+static int patch_vt1708(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ /* override some patch_ops */
+ codec->patch_ops.build_controls = vt1708_build_controls;
+ codec->patch_ops.build_pcms = vt1708_build_pcms;
+ spec->gen.mixer_nid = 0x17;
+
+ /* set jackpoll_interval while parsing the codec */
+ codec->jackpoll_interval = msecs_to_jiffies(100);
+ spec->vt1708_jack_detect = 1;
+
+ /* don't support the input jack switching due to lack of unsol event */
+ /* (it may work with polling, though, but it needs testing) */
+ spec->gen.suppress_auto_mic = 1;
+ /* Some machines show the broken speaker mute */
+ spec->gen.auto_mute_via_amp = 1;
+
+ /* Add HP and CD pin config connect bit re-config action */
+ vt1708_set_pinconfig_connect(codec, VT1708_HP_PIN_NID);
+ vt1708_set_pinconfig_connect(codec, VT1708_CD_PIN_NID);
+
+ err = snd_hda_add_verbs(codec, vt1708_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ /* add jack detect on/off control */
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1708_jack_detect_ctl)) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ /* clear jackpoll_interval again; it's set dynamically */
+ codec->jackpoll_interval = 0;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+static int patch_vt1709(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x18;
+
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+static int patch_vt1708S(struct hda_codec *codec);
+static int patch_vt1708B(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ if (get_codec_type(codec) == VT1708BCE)
+ return patch_vt1708S(codec);
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x16;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* Patch for VT1708S */
+static const struct hda_verb vt1708S_init_verbs[] = {
+ /* Enable Mic Boost Volume backdoor */
+ {0x1, 0xf98, 0x1},
+ /* don't bybass mixer */
+ {0x1, 0xf88, 0xc0},
+ { }
+};
+
+static void override_mic_boost(struct hda_codec *codec, hda_nid_t pin,
+ int offset, int num_steps, int step_size)
+{
+ snd_hda_override_wcaps(codec, pin,
+ get_wcaps(codec, pin) | AC_WCAP_IN_AMP);
+ snd_hda_override_amp_caps(codec, pin, HDA_INPUT,
+ (offset << AC_AMPCAP_OFFSET_SHIFT) |
+ (num_steps << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (step_size << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+static int patch_vt1708S(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x16;
+ override_mic_boost(codec, 0x1a, 0, 3, 40);
+ override_mic_boost(codec, 0x1e, 0, 3, 40);
+
+ /* correct names for VT1708BCE */
+ if (get_codec_type(codec) == VT1708BCE)
+ snd_hda_codec_set_name(codec, "VT1708BCE");
+ /* correct names for VT1705 */
+ if (codec->core.vendor_id == 0x11064397)
+ snd_hda_codec_set_name(codec, "VT1705");
+
+ err = snd_hda_add_verbs(codec, vt1708S_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* Patch for VT1702 */
+
+static const struct hda_verb vt1702_init_verbs[] = {
+ /* mixer enable */
+ {0x1, 0xF88, 0x3},
+ /* GPIO 0~2 */
+ {0x1, 0xF82, 0x3F},
+ { }
+};
+
+static int patch_vt1702(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x1a;
+
+ /* limit AA path volume to 0 dB */
+ snd_hda_override_amp_caps(codec, 0x1A, HDA_INPUT,
+ (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x5 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (1 << AC_AMPCAP_MUTE_SHIFT));
+
+ err = snd_hda_add_verbs(codec, vt1702_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* Patch for VT1718S */
+
+static const struct hda_verb vt1718S_init_verbs[] = {
+ /* Enable MW0 adjust Gain 5 */
+ {0x1, 0xfb2, 0x10},
+ /* Enable Boost Volume backdoor */
+ {0x1, 0xf88, 0x8},
+
+ { }
+};
+
+/* Add a connection to the primary DAC from AA-mixer for some codecs
+ * This isn't listed from the raw info, but the chip has a secret connection.
+ */
+static int add_secret_dac_path(struct hda_codec *codec)
+{
+ struct via_spec *spec = codec->spec;
+ int i, nums;
+ hda_nid_t conn[8];
+ hda_nid_t nid;
+
+ if (!spec->gen.mixer_nid)
+ return 0;
+ nums = snd_hda_get_connections(codec, spec->gen.mixer_nid, conn,
+ ARRAY_SIZE(conn) - 1);
+ if (nums < 0)
+ return nums;
+
+ for (i = 0; i < nums; i++) {
+ if (get_wcaps_type(get_wcaps(codec, conn[i])) == AC_WID_AUD_OUT)
+ return 0;
+ }
+
+ /* find the primary DAC and add to the connection list */
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int caps = get_wcaps(codec, nid);
+ if (get_wcaps_type(caps) == AC_WID_AUD_OUT &&
+ !(caps & AC_WCAP_DIGITAL)) {
+ conn[nums++] = nid;
+ return snd_hda_override_conn_list(codec,
+ spec->gen.mixer_nid,
+ nums, conn);
+ }
+ }
+ return 0;
+}
+
+
+static int patch_vt1718S(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x21;
+ override_mic_boost(codec, 0x2b, 0, 3, 40);
+ override_mic_boost(codec, 0x29, 0, 3, 40);
+ add_secret_dac_path(codec);
+
+ err = snd_hda_add_verbs(codec, vt1718S_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* Patch for VT1716S */
+
+static int vt1716s_dmic_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 vt1716s_dmic_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int index = 0;
+
+ index = snd_hda_codec_read(codec, 0x26, 0,
+ AC_VERB_GET_CONNECT_SEL, 0);
+ if (index != -1)
+ *ucontrol->value.integer.value = index;
+
+ return 0;
+}
+
+static int vt1716s_dmic_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct via_spec *spec = codec->spec;
+ int index = *ucontrol->value.integer.value;
+
+ snd_hda_codec_write(codec, 0x26, 0,
+ AC_VERB_SET_CONNECT_SEL, index);
+ spec->dmic_enabled = index;
+ return 1;
+}
+
+static const struct snd_kcontrol_new vt1716s_dmic_mixer_vol =
+ HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x22, 0x0, HDA_INPUT);
+static const struct snd_kcontrol_new vt1716s_dmic_mixer_sw = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Digital Mic Capture Switch",
+ .subdevice = HDA_SUBDEV_NID_FLAG | 0x26,
+ .count = 1,
+ .info = vt1716s_dmic_info,
+ .get = vt1716s_dmic_get,
+ .put = vt1716s_dmic_put,
+};
+
+
+/* mono-out mixer elements */
+static const struct snd_kcontrol_new vt1716S_mono_out_mixer =
+ HDA_CODEC_MUTE("Mono Playback Switch", 0x2a, 0x0, HDA_OUTPUT);
+
+static const struct hda_verb vt1716S_init_verbs[] = {
+ /* Enable Boost Volume backdoor */
+ {0x1, 0xf8a, 0x80},
+ /* don't bybass mixer */
+ {0x1, 0xf88, 0xc0},
+ /* Enable mono output */
+ {0x1, 0xf90, 0x08},
+ { }
+};
+
+static int patch_vt1716S(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x16;
+ override_mic_boost(codec, 0x1a, 0, 3, 40);
+ override_mic_boost(codec, 0x1e, 0, 3, 40);
+
+ err = snd_hda_add_verbs(codec, vt1716S_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1716s_dmic_mixer_vol) ||
+ !snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1716s_dmic_mixer_sw) ||
+ !snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1716S_mono_out_mixer)) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* for vt2002P */
+
+static const struct hda_verb vt2002P_init_verbs[] = {
+ /* Class-D speaker related verbs */
+ {0x1, 0xfe0, 0x4},
+ {0x1, 0xfe9, 0x80},
+ {0x1, 0xfe2, 0x22},
+ /* Enable Boost Volume backdoor */
+ {0x1, 0xfb9, 0x24},
+ /* Enable AOW0 to MW9 */
+ {0x1, 0xfb8, 0x88},
+ { }
+};
+
+static const struct hda_verb vt1802_init_verbs[] = {
+ /* Enable Boost Volume backdoor */
+ {0x1, 0xfb9, 0x24},
+ /* Enable AOW0 to MW9 */
+ {0x1, 0xfb8, 0x88},
+ { }
+};
+
+/*
+ * pin fix-up
+ */
+enum {
+ VIA_FIXUP_INTMIC_BOOST,
+ VIA_FIXUP_ASUS_G75,
+ VIA_FIXUP_POWER_SAVE,
+};
+
+static void via_fixup_intmic_boost(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ override_mic_boost(codec, 0x30, 0, 2, 40);
+}
+
+static void via_fixup_power_save(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ codec->power_save_node = 0;
+}
+
+static const struct hda_fixup via_fixups[] = {
+ [VIA_FIXUP_INTMIC_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = via_fixup_intmic_boost,
+ },
+ [VIA_FIXUP_ASUS_G75] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* set 0x24 and 0x33 as speakers */
+ { 0x24, 0x991301f0 },
+ { 0x33, 0x991301f1 }, /* subwoofer */
+ { }
+ }
+ },
+ [VIA_FIXUP_POWER_SAVE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = via_fixup_power_save,
+ },
+};
+
+static const struct snd_pci_quirk vt2002p_fixups[] = {
+ SND_PCI_QUIRK(0x1043, 0x13f7, "Asus B23E", VIA_FIXUP_POWER_SAVE),
+ SND_PCI_QUIRK(0x1043, 0x1487, "Asus G75", VIA_FIXUP_ASUS_G75),
+ SND_PCI_QUIRK(0x1043, 0x8532, "Asus X202E", VIA_FIXUP_INTMIC_BOOST),
+ SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", VIA_FIXUP_POWER_SAVE),
+ {}
+};
+
+/* NIDs 0x24 and 0x33 on VT1802 have connections to non-existing NID 0x3e
+ * Replace this with mixer NID 0x1c
+ */
+static void fix_vt1802_connections(struct hda_codec *codec)
+{
+ static const hda_nid_t conn_24[] = { 0x14, 0x1c };
+ static const hda_nid_t conn_33[] = { 0x1c };
+
+ snd_hda_override_conn_list(codec, 0x24, ARRAY_SIZE(conn_24), conn_24);
+ snd_hda_override_conn_list(codec, 0x33, ARRAY_SIZE(conn_33), conn_33);
+}
+
+/* patch for vt2002P */
+static int patch_vt2002P(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x21;
+ override_mic_boost(codec, 0x2b, 0, 3, 40);
+ override_mic_boost(codec, 0x29, 0, 3, 40);
+ if (spec->codec_type == VT1802)
+ fix_vt1802_connections(codec);
+ add_secret_dac_path(codec);
+
+ snd_hda_pick_fixup(codec, NULL, vt2002p_fixups, via_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ if (spec->codec_type == VT1802)
+ err = snd_hda_add_verbs(codec, vt1802_init_verbs);
+ else
+ err = snd_hda_add_verbs(codec, vt2002P_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* for vt1812 */
+
+static const struct hda_verb vt1812_init_verbs[] = {
+ /* Enable Boost Volume backdoor */
+ {0x1, 0xfb9, 0x24},
+ /* Enable AOW0 to MW9 */
+ {0x1, 0xfb8, 0xa8},
+ { }
+};
+
+/* patch for vt1812 */
+static int patch_vt1812(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x21;
+ override_mic_boost(codec, 0x2b, 0, 3, 40);
+ override_mic_boost(codec, 0x29, 0, 3, 40);
+ add_secret_dac_path(codec);
+
+ err = snd_hda_add_verbs(codec, vt1812_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/* patch for vt3476 */
+
+static const struct hda_verb vt3476_init_verbs[] = {
+ /* Enable DMic 8/16/32K */
+ {0x1, 0xF7B, 0x30},
+ /* Enable Boost Volume backdoor */
+ {0x1, 0xFB9, 0x20},
+ /* Enable AOW-MW9 path */
+ {0x1, 0xFB8, 0x10},
+ { }
+};
+
+static int patch_vt3476(struct hda_codec *codec)
+{
+ struct via_spec *spec;
+ int err;
+
+ /* create a codec specific record */
+ spec = via_new_spec(codec);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ spec->gen.mixer_nid = 0x3f;
+ add_secret_dac_path(codec);
+
+ err = snd_hda_add_verbs(codec, vt3476_init_verbs);
+ if (err < 0)
+ goto error;
+
+ /* automatic parse from the BIOS config */
+ err = via_parse_auto_config(codec);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ via_free(codec);
+ return err;
+}
+
+/*
+ * patch entries
+ */
+static const struct hda_device_id snd_hda_id_via[] = {
+ HDA_CODEC_ENTRY(0x11061708, "VT1708", patch_vt1708),
+ HDA_CODEC_ENTRY(0x11061709, "VT1708", patch_vt1708),
+ HDA_CODEC_ENTRY(0x1106170a, "VT1708", patch_vt1708),
+ HDA_CODEC_ENTRY(0x1106170b, "VT1708", patch_vt1708),
+ HDA_CODEC_ENTRY(0x1106e710, "VT1709 10-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e711, "VT1709 10-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e712, "VT1709 10-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e713, "VT1709 10-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e714, "VT1709 6-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e715, "VT1709 6-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e716, "VT1709 6-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e717, "VT1709 6-Ch", patch_vt1709),
+ HDA_CODEC_ENTRY(0x1106e720, "VT1708B 8-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e721, "VT1708B 8-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e722, "VT1708B 8-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e723, "VT1708B 8-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e724, "VT1708B 4-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e725, "VT1708B 4-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e726, "VT1708B 4-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x1106e727, "VT1708B 4-Ch", patch_vt1708B),
+ HDA_CODEC_ENTRY(0x11060397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11061397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11062397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11063397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11064397, "VT1705", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11065397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11066397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11067397, "VT1708S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11060398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11061398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11062398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11063398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11064398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11065398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11066398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11067398, "VT1702", patch_vt1702),
+ HDA_CODEC_ENTRY(0x11060428, "VT1718S", patch_vt1718S),
+ HDA_CODEC_ENTRY(0x11064428, "VT1718S", patch_vt1718S),
+ HDA_CODEC_ENTRY(0x11060441, "VT2020", patch_vt1718S),
+ HDA_CODEC_ENTRY(0x11064441, "VT1828S", patch_vt1718S),
+ HDA_CODEC_ENTRY(0x11060433, "VT1716S", patch_vt1716S),
+ HDA_CODEC_ENTRY(0x1106a721, "VT1716S", patch_vt1716S),
+ HDA_CODEC_ENTRY(0x11060438, "VT2002P", patch_vt2002P),
+ HDA_CODEC_ENTRY(0x11064438, "VT2002P", patch_vt2002P),
+ HDA_CODEC_ENTRY(0x11060448, "VT1812", patch_vt1812),
+ HDA_CODEC_ENTRY(0x11060440, "VT1818S", patch_vt1708S),
+ HDA_CODEC_ENTRY(0x11060446, "VT1802", patch_vt2002P),
+ HDA_CODEC_ENTRY(0x11068446, "VT1802", patch_vt2002P),
+ HDA_CODEC_ENTRY(0x11064760, "VT1705CF", patch_vt3476),
+ HDA_CODEC_ENTRY(0x11064761, "VT1708SCE", patch_vt3476),
+ HDA_CODEC_ENTRY(0x11064762, "VT1808", patch_vt3476),
+ {} /* terminator */
+};
+MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_via);
+
+static struct hda_codec_driver via_driver = {
+ .id = snd_hda_id_via,
+};
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("VIA HD-audio codec");
+
+module_hda_codec_driver(via_driver);
diff --git a/sound/pci/hda/thinkpad_helper.c b/sound/pci/hda/thinkpad_helper.c
new file mode 100644
index 000000000..6698ae241
--- /dev/null
+++ b/sound/pci/hda/thinkpad_helper.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Helper functions for Thinkpad LED control;
+ * to be included from codec driver
+ */
+
+#if IS_ENABLED(CONFIG_THINKPAD_ACPI)
+
+#include <linux/acpi.h>
+#include <linux/leds.h>
+
+static bool is_thinkpad(struct hda_codec *codec)
+{
+ return (codec->core.subsystem_id >> 16 == 0x17aa) &&
+ (acpi_dev_found("LEN0068") || acpi_dev_found("LEN0268") ||
+ acpi_dev_found("IBM0068"));
+}
+
+static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ if (!is_thinkpad(codec))
+ return;
+ snd_hda_gen_add_mute_led_cdev(codec, NULL);
+ snd_hda_gen_add_micmute_led_cdev(codec, NULL);
+ }
+}
+
+#else /* CONFIG_THINKPAD_ACPI */
+
+static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+}
+
+#endif /* CONFIG_THINKPAD_ACPI */
diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile
new file mode 100644
index 000000000..1196f22a9
--- /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 <perex@perex.cz>
+#
+
+snd-ice17xx-ak4xxx-objs := ak4xxx.o
+snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o
+snd-ice1724-objs := 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..cad33a2f2
--- /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 <perex@perex.cz>
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "ice1712.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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(sizeof(*priv), GFP_KERNEL);
+ 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 <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#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 <perex@perex.cz>
+ */
+
+#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..40a0e0095
--- /dev/null
+++ b/sound/pci/ice1712/aureon.c
@@ -0,0 +1,2267 @@
+// 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 <tiwai@suse.de>
+ *
+ * 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 <apostol@cs.utoronto.ca>
+ *
+ * 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 <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "aureon.h"
+#include <sound/tlv.h>
+
+/* 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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+
+ ucontrol->value.integer.value[0] = aureon_ac97_read(ice,
+ kcontrol->private_value & 0x7F) & 0x8000 ? 0 : 1;
+
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+
+ ucontrol->value.integer.value[0] = aureon_ac97_read(ice, AC97_MIC) & 0x0020 ? 0 : 1;
+
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX1) >> 1) & 0x01;
+
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ? 0 : 1;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&ice->gpio_mutex);
+ 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 if ((id & 0x0F) != 0x01)
+ dev_info(ice->card->dev,
+ "Detected unsupported CS8415 rev. (%c)\n",
+ (char)((id & 0x0F) + 'A' - 1));
+ else {
+ for (i = 0; i < ARRAY_SIZE(cs8415_controls); i++) {
+ struct snd_kcontrol *kctl;
+ err = snd_ctl_add(ice->card, (kctl = snd_ctl_new1(&cs8415_controls[i], ice)));
+ if (err < 0)
+ return err;
+ if (i > 1)
+ kctl->id.device = ice->pcm->device;
+ }
+ }
+ }
+
+ 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(sizeof(*spec), GFP_KERNEL);
+ 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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 <tiwai@suse.de>
+ */
+
+#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..1d2a02872
--- /dev/null
+++ b/sound/pci/ice1712/delta.c
@@ -0,0 +1,920 @@
+// 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 <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "delta.h"
+
+#define SND_CS8403
+#include <sound/cs8403.h>
+
+
+/*
+ * 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+
+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);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_bits != val;
+ ice->spdif.cs8403_bits = val;
+ if (change && ice->playback_pro_substream == NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_delta_cs8403_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ 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);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_stream_bits != val;
+ ice->spdif.cs8403_stream_bits = val;
+ if (change && ice->playback_pro_substream != NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_delta_cs8403_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+/*
+ * 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 */
+ mutex_lock(&ice->gpio_mutex);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ mutex_unlock(&ice->gpio_mutex);
+ tmp2 = tmp & ~ICE1712_DELTA_DFS;
+ if (rate > 48000)
+ tmp2 |= ICE1712_DELTA_DFS;
+ if (tmp == tmp2)
+ return;
+
+ /* do it again */
+ snd_akm4xxx_reset(ak, 1);
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+ 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 long flags;
+ unsigned int tmp;
+ int change;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ 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, &reg, 1) != 1)
+ dev_err(ice->card->dev,
+ "unable to send register 0x%x byte to CS8427\n", reg);
+ snd_i2c_readbytes(ice->cs8427, &reg, 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:
+ if ((err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c)) < 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;
+ if ((err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR)) < 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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 <perex@perex.cz>
+ */
+
+#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 <Kristof.Pelckmans@antwerpen.be> 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 <ajh@watri.uwa.edu.au> */
+/* 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 <perex@perex.cz>
+ */
+
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/rawmidi.h>
+#include <sound/i2c.h>
+#include <sound/pcm.h>
+
+#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..379430831
--- /dev/null
+++ b/sound/pci/ice1712/ews.c
@@ -0,0 +1,1067 @@
+// 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 <perex@perex.cz>
+ * 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "ews.h"
+
+#define SND_CS8404
+#include <sound/cs8403.h>
+
+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);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_bits != val;
+ ice->spdif.cs8403_bits = val;
+ if (change && ice->playback_pro_substream == NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_ews_cs8404_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ 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);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_stream_bits != val;
+ ice->spdif.cs8403_stream_bits = val;
+ if (change && ice->playback_pro_substream != NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_ews_cs8404_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ 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 long flags;
+ unsigned char tmp;
+ int change;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ 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(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ /* create i2c */
+ if ((err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c)) < 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 */
+ if ((err = snd_ice1712_ews88mt_chip_select(ice, 0x0f)) < 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:
+ if ((err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR)) < 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:
+ if ((err = snd_ice1712_init_cs8427(ice, ICE1712_6FIRE_CS8427_ADDR)) < 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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;
+
+ if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 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;
+
+ if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 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;
+
+ if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 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;
+
+ if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 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 <perex@perex.cz>
+ * 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+#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..46daeea8d
--- /dev/null
+++ b/sound/pci/ice1712/hoontech.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for Hoontech STDSP24
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#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;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_0_DAREAR(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_mute(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_3_MUTE(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_insel(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_3_INSEL(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_box_channel(struct snd_ice1712 *ice, int box, int chn, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+
+ mutex_lock(&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]);
+
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_box_midi(struct snd_ice1712 *ice, int box, int master)
+{
+ struct hoontech_spec *spec = ice->spec;
+
+ mutex_lock(&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]);
+
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_midi2(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_3_MIDI2(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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(sizeof(*spec), GFP_KERNEL);
+ 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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 <perex@perex.cz>
+ */
+
+#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..73e1e5400
--- /dev/null
+++ b/sound/pci/ice1712/ice1712.c
@@ -0,0 +1,2826 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+/*
+ NOTES:
+ - spdif nonaudio consumer mode does not work (at least with my
+ Sony STR-DB830)
+*/
+
+/*
+ * Changes:
+ *
+ * 2002.09.09 Takashi Iwai <tiwai@suse.de>
+ * 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 <jstafford@ampltd.com>
+ * 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 <tiwai@suse.de>
+ * Split vt1724 part to an independent driver.
+ * The GPIO is accessed through the callback functions now.
+ *
+ * 2004.03.31 Doug McLain <nostar@comcast.net>
+ * Added support for Event Electronics EZ8 card to hoontech.c.
+ */
+
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+
+/* lowlevel routines */
+#include "delta.h"
+#include "ews.h"
+#include "hoontech.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ICEnsemble ICE1712 (Envy24)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{"
+ HOONTECH_DEVICE_DESC
+ DELTA_DEVICE_DESC
+ EWS_DEVICE_DESC
+ "{ICEnsemble,Generic ICE1712},"
+ "{ICEnsemble,Generic Envy24}}");
+
+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), 0 }, /* ICE1712 */
+ { 0, }
+};
+
+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;
+
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock(&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);
+ spin_unlock(&ice->reg_lock);
+ 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;
+
+ spin_lock(&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);
+ spin_unlock(&ice->reg_lock);
+ 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;
+
+ spin_lock(&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);
+ spin_unlock(&ice->reg_lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ spin_lock_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);
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ spin_lock_irq(&ice->reg_lock);
+ tmp = inw(ICEDS(ice, INTMASK)) & ~(1 << (substream->number * 2));
+ outw(tmp, ICEDS(ice, INTMASK));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_irq(&ice->reg_lock);
+ tmp = inw(ICEDS(ice, INTMASK)) | (3 << (substream->number * 2));
+ outw(tmp, ICEDS(ice, INTMASK));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ strcpy(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;
+ strcpy(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);
+ spin_lock(&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));
+ spin_unlock(&ice->reg_lock);
+ 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);
+ }
+ }
+ spin_lock(&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));
+ spin_unlock(&ice->reg_lock);
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ */
+static void snd_ice1712_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, int force)
+{
+ unsigned long flags;
+ 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;
+ }
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if (inb(ICEMT(ice, PLAYBACK_CONTROL)) & (ICE1712_CAPTURE_START_SHADOW|
+ ICE1712_PLAYBACK_PAUSE|
+ ICE1712_PLAYBACK_START)) {
+__out:
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ return;
+ }
+ if (!force && is_pro_rate_locked(ice))
+ goto __out;
+
+ old = inb(ICEMT(ice, RATE));
+ if (!force && old == val)
+ goto __out;
+
+ ice->cur_rate = rate;
+ outb(val, ICEMT(ice, RATE));
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ strcpy(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;
+
+ spin_lock_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);
+ spin_unlock_irq(&ice->reg_lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&ice->reg_lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_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;
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_LOCKED != nval;
+ PRO_RATE_LOCKED = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_RESET != nval;
+ PRO_RATE_RESET = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_irq(&ice->reg_lock);
+ val = inw(ICEMT(ice, ROUTE_PSDOUT03));
+ cval = inl(ICEMT(ice, ROUTE_CAPTURE));
+ spin_unlock_irq(&ice->reg_lock);
+
+ 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);
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+ if (nval < 2) /* dig mixer of pcm */
+ return change;
+
+ /* update CAPTURE */
+ spin_lock_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));
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ 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 */
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_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));
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_default, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_maskc, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_maskp, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_stream, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm_pro->device;
+ 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 int snd_ice1712_free(struct snd_ice1712 *ice)
+{
+ if (!ice->port)
+ goto __hw_end;
+ /* mask all interrupts */
+ outb(ICE1712_MULTI_CAPTURE | ICE1712_MULTI_PLAYBACK, ICEMT(ice, IRQ));
+ outb(0xff, ICEREG(ice, IRQMASK));
+ /* --- */
+__hw_end:
+ if (ice->irq >= 0)
+ free_irq(ice->irq, ice);
+
+ if (ice->port)
+ pci_release_regions(ice->pci);
+ snd_ice1712_akm4xxx_free(ice);
+ pci_disable_device(ice->pci);
+ kfree(ice->spec);
+ kfree(ice);
+ return 0;
+}
+
+static int snd_ice1712_dev_free(struct snd_device *device)
+{
+ struct snd_ice1712 *ice = device->device_data;
+ return snd_ice1712_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 **r_ice1712)
+{
+ struct snd_ice1712 *ice;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_ice1712_dev_free,
+ };
+
+ *r_ice1712 = NULL;
+
+ /* enable PCI device */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 28 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(28)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(28)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 28bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ ice = kzalloc(sizeof(*ice), GFP_KERNEL);
+ if (ice == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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);
+
+ card->private_data = ice;
+
+ err = pci_request_regions(pci, "ICE1712");
+ if (err < 0) {
+ kfree(ice);
+ pci_disable_device(pci);
+ 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 (request_irq(pci->irq, snd_ice1712_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, ice)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_ice1712_free(ice);
+ return -EIO;
+ }
+
+ ice->irq = pci->irq;
+ card->sync_irq = ice->irq;
+
+ if (snd_ice1712_read_eeprom(ice, modelname) < 0) {
+ snd_ice1712_free(ice);
+ return -EIO;
+ }
+ if (snd_ice1712_chip_init(ice) < 0) {
+ snd_ice1712_free(ice);
+ return -EIO;
+ }
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ice, &ops);
+ if (err < 0) {
+ snd_ice1712_free(ice);
+ return err;
+ }
+
+ *r_ice1712 = ice;
+ 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;
+ 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, "ICE1712");
+ strcpy(card->shortname, "ICEnsemble ICE1712");
+
+ err = snd_ice1712_create(card, pci, model[dev], omni[dev],
+ cs8427_timeout[dev], dxr_enable[dev], &ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ for (tbl = card_tables; *tbl; tbl++) {
+ for (c = *tbl; c->subvendor; c++) {
+ if (c->subvendor == ice->eeprom.subvendor) {
+ ice->card_info = c;
+ strcpy(card->shortname, c->name);
+ if (c->driver) /* specific driver? */
+ strcpy(card->driver, c->driver);
+ if (c->chip_init) {
+ err = c->chip_init(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+ goto __found;
+ }
+ }
+ }
+ c = &no_matched;
+ __found:
+
+ err = snd_ice1712_pcm_profi(ice, pcm_dev++);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if (ice_has_con_ac97(ice)) {
+ err = snd_ice1712_pcm(ice, pcm_dev++);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+
+ err = snd_ice1712_ac97_mixer(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ err = snd_ice1712_build_controls(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if (c->build_controls) {
+ err = c->build_controls(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+
+ if (ice_has_con_ac97(ice)) {
+ err = snd_ice1712_pcm_ds(ice, pcm_dev++);
+ if (err < 0) {
+ snd_card_free(card);
+ 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) {
+ snd_card_free(card);
+ 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) {
+ snd_card_free(card);
+ 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) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_ice1712_remove(struct pci_dev *pci)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct snd_ice1712 *ice = card->private_data;
+
+ if (ice->card_info && ice->card_info->chip_exit)
+ ice->card_info->chip_exit(ice);
+ snd_card_free(card);
+}
+
+#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);
+
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+
+ 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 */
+ spin_lock_irq(&ice->reg_lock);
+ outb(inb(ICEMT(ice, RATE)) | ICE1712_SPDIF_MASTER,
+ ICEMT(ice, RATE));
+ spin_unlock_irq(&ice->reg_lock);
+ 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,
+ .remove = snd_ice1712_remove,
+ .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 <perex@perex.cz>
+ */
+
+#include <linux/io.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/rawmidi.h>
+#include <sound/i2c.h>
+#include <sound/ak4xxx-adda.h>
+#include <sound/ak4114.h>
+#include <sound/pt2258.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+
+
+/*
+ * 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..c0fca94c1
--- /dev/null
+++ b/sound/pci/ice1712/ice1724.c
@@ -0,0 +1,2853 @@
+// 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 <perex@perex.cz>
+ * 2002 James Stafford <jstafford@ampltd.com>
+ * 2003 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+
+#include <sound/asoundef.h>
+
+#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 <perex@perex.cz>");
+MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{"
+ REVO_DEVICE_DESC
+ AMP_AUDIO2000_DEVICE_DESC
+ AUREON_DEVICE_DESC
+ VT1720_MOBO_DEVICE_DESC
+ PONTIS_DEVICE_DESC
+ PRODIGY192_DEVICE_DESC
+ PRODIGY_HIFI_DEVICE_DESC
+ JULI_DEVICE_DESC
+ MAYA44_DEVICE_DESC
+ PHASE_DEVICE_DESC
+ WTM_DEVICE_DESC
+ SE_DEVICE_DESC
+ QTET_DEVICE_DESC
+ "{VIA,VT1720},"
+ "{VIA,VT1724},"
+ "{ICEnsemble,Generic ICE1724},"
+ "{ICEnsemble,Generic Envy24HT}"
+ "{ICEnsemble,Generic Envy24PT}}");
+
+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), 0 },
+ { 0, }
+};
+
+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;
+
+ spin_lock_irq(&ice->reg_lock);
+ enable_midi_irq(ice, flag, enable);
+ spin_unlock_irq(&ice->reg_lock);
+}
+
+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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if (up) {
+ ice->midi_output = 1;
+ vt1724_midi_write(ice);
+ } else {
+ ice->midi_output = 0;
+ enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+ }
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if (up) {
+ ice->midi_input = 1;
+ vt1724_midi_read(ice);
+ } else {
+ ice->midi_input = 0;
+ }
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+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;
+ spin_lock(&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);
+ }
+ spin_unlock(&ice->reg_lock);
+ break;
+ }
+ 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));
+ spin_unlock(&ice->reg_lock);
+ 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 */
+ }
+
+ }
+ }
+ 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:
+ spin_lock(&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));
+ spin_unlock(&ice->reg_lock);
+ break;
+
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ spin_lock(&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));
+ spin_unlock(&ice->reg_lock);
+ 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 long flags;
+ 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;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if ((inb(ICEMT1724(ice, DMA_CONTROL)) & DMA_STARTS) ||
+ (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) {
+ /* running? we cannot change the rate now... */
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ 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 */
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ 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) {
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ return 0;
+ }
+ }
+
+ ice->cur_rate = rate;
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+
+ 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);
+ mutex_lock(&ice->open_mutex);
+ /* 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) {
+ mutex_unlock(&ice->open_mutex);
+ 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) {
+ mutex_unlock(&ice->open_mutex);
+ return -EBUSY;
+ }
+ ice->pcm_reserved[i] = substream;
+ break;
+ }
+ }
+ }
+ mutex_unlock(&ice->open_mutex);
+
+ 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);
+ int i;
+
+ mutex_lock(&ice->open_mutex);
+ /* unmark surround channels */
+ for (i = 0; i < 3; i++)
+ if (ice->pcm_reserved[i] == substream)
+ ice->pcm_reserved[i] = NULL;
+ mutex_unlock(&ice->open_mutex);
+ 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;
+
+ spin_lock_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);
+
+ spin_unlock_irq(&ice->reg_lock);
+
+ /*
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&ice->reg_lock);
+ 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_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);
+ mutex_lock(&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);
+ mutex_unlock(&ice->open_mutex);
+ 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;
+ strcpy(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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+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;
+ strcpy(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;
+
+ spin_lock_irq(&ice->reg_lock);
+ val = 3 - substream->number;
+ if (inb(ICEMT1724(ice, BURST)) < val)
+ outb(val, ICEMT1724(ice, BURST));
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ mutex_lock(&ice->open_mutex);
+ /* already used by PDMA0? */
+ if (ice->pcm_reserved[substream->number]) {
+ mutex_unlock(&ice->open_mutex);
+ return -EBUSY; /* FIXME: should handle blocking mode properly */
+ }
+ mutex_unlock(&ice->open_mutex);
+ 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;
+ strcpy(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);
+ spin_lock_irq(&ice->reg_lock);
+ old = inw(ICEMT1724(ice, SPDIF_CTRL));
+ if (val != old)
+ update_spdif_bits(ice, val);
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+ 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 */
+ strcpy(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;
+
+ spin_lock_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;
+ }
+ }
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ if (item > first_ext_clock + ice->ext_clock_count - 1)
+ return -EINVAL;
+
+ /* if rate = 0 => external clock */
+ spin_lock_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;
+ spin_unlock_irq(&ice->reg_lock);
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 1);
+ spin_lock_irq(&ice->reg_lock);
+ }
+ spin_unlock_irq(&ice->reg_lock);
+
+ /* 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;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_LOCKED != nval;
+ PRO_RATE_LOCKED = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_RESET != nval;
+ PRO_RATE_RESET = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ 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;
+
+ spin_lock_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));
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ 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
+};
+
+/*
+ *
+ */
+
+static const struct snd_ice1712_card_info no_matched;
+
+
+/*
+ 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;
+
+ mutex_lock(&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));
+ mutex_unlock(&ice->i2c_mutex);
+ /*
+ 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)
+{
+ mutex_lock(&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);
+ mutex_unlock(&ice->i2c_mutex);
+}
+
+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;
+
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_default, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_maskc, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_maskp, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm->device;
+#if 0 /* use default only */
+ err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_stream, ice));
+ if (err < 0)
+ return err;
+ kctl->id.device = ice->pcm->device;
+ 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 int snd_vt1724_free(struct snd_ice1712 *ice)
+{
+ if (!ice->port)
+ goto __hw_end;
+ /* mask all interrupts */
+ outb(0xff, ICEMT1724(ice, DMA_INT_MASK));
+ outb(0xff, ICEREG1724(ice, IRQMASK));
+ /* --- */
+__hw_end:
+ if (ice->irq >= 0)
+ free_irq(ice->irq, ice);
+ pci_release_regions(ice->pci);
+ snd_ice1712_akm4xxx_free(ice);
+ pci_disable_device(ice->pci);
+ kfree(ice->spec);
+ kfree(ice);
+ return 0;
+}
+
+static int snd_vt1724_dev_free(struct snd_device *device)
+{
+ struct snd_ice1712 *ice = device->device_data;
+ return snd_vt1724_free(ice);
+}
+
+static int snd_vt1724_create(struct snd_card *card,
+ struct pci_dev *pci,
+ const char *modelname,
+ struct snd_ice1712 **r_ice1712)
+{
+ struct snd_ice1712 *ice;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_vt1724_dev_free,
+ };
+
+ *r_ice1712 = NULL;
+
+ /* enable PCI device */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ ice = kzalloc(sizeof(*ice), GFP_KERNEL);
+ if (ice == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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);
+
+ card->private_data = ice;
+
+ err = pci_request_regions(pci, "ICE1724");
+ if (err < 0) {
+ kfree(ice);
+ pci_disable_device(pci);
+ return err;
+ }
+ ice->port = pci_resource_start(pci, 0);
+ ice->profi_port = pci_resource_start(pci, 1);
+
+ if (request_irq(pci->irq, snd_vt1724_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, ice)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_vt1724_free(ice);
+ return -EIO;
+ }
+
+ ice->irq = pci->irq;
+ card->sync_irq = ice->irq;
+
+ snd_vt1724_chip_reset(ice);
+ if (snd_vt1724_read_eeprom(ice, modelname) < 0) {
+ snd_vt1724_free(ice);
+ return -EIO;
+ }
+ if (snd_vt1724_chip_init(ice) < 0) {
+ snd_vt1724_free(ice);
+ return -EIO;
+ }
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ice, &ops);
+ if (err < 0) {
+ snd_vt1724_free(ice);
+ return err;
+ }
+
+ *r_ice1712 = ice;
+ 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 * const *tbl, *c;
+
+ 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 < 0)
+ return err;
+
+ strcpy(card->driver, "ICE1724");
+ strcpy(card->shortname, "ICEnsemble ICE1724");
+
+ err = snd_vt1724_create(card, pci, model[dev], &ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* field init before calling chip_init */
+ ice->ext_clock_count = 0;
+
+ for (tbl = card_tables; *tbl; tbl++) {
+ for (c = *tbl; c->name; c++) {
+ if ((model[dev] && c->model &&
+ !strcmp(model[dev], c->model)) ||
+ (c->subvendor == ice->eeprom.subvendor)) {
+ strcpy(card->shortname, c->name);
+ if (c->driver) /* specific driver? */
+ strcpy(card->driver, c->driver);
+ if (c->chip_init) {
+ err = c->chip_init(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+ goto __found;
+ }
+ }
+ }
+ c = &no_matched;
+__found:
+ /*
+ * 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) {
+ snd_card_free(card);
+ return err;
+ }
+
+ err = snd_vt1724_pcm_spdif(ice, pcm_dev++);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ err = snd_vt1724_pcm_indep(ice, pcm_dev++);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ err = snd_vt1724_ac97_mixer(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ err = snd_vt1724_build_controls(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if (ice->pcm && ice->has_spdif) { /* has SPDIF I/O */
+ err = snd_vt1724_spdif_build_controls(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+
+ if (c->build_controls) {
+ err = c->build_controls(ice);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+
+ if (!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) {
+ snd_card_free(card);
+ return err;
+ }
+ ice->rmidi[0] = rmidi;
+ rmidi->private_data = ice;
+ strcpy(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) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_vt1724_remove(struct pci_dev *pci)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+ struct snd_ice1712 *ice = card->private_data;
+
+ if (ice->card_info && ice->card_info->chip_exit)
+ ice->card_info->chip_exit(ice);
+ snd_card_free(card);
+}
+
+#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);
+
+ spin_lock_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));
+ spin_unlock_irq(&ice->reg_lock);
+
+ 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,
+ .remove = snd_vt1724_remove,
+ .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..e57a55ceb
--- /dev/null
+++ b/sound/pci/ice1712/juli.c
@@ -0,0 +1,684 @@
+// 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 <perex@perex.cz>
+ * 2008 Pavel Hofman <dustin@seznam.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+
+#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 struct snd_kcontrol *ctl_find(struct snd_card *card,
+ const char *name)
+{
+ struct snd_ctl_elem_id sid = {0};
+
+ strlcpy(sid.name, name, sizeof(sid.name));
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_find_id(card, &sid);
+}
+
+static void add_followers(struct snd_card *card,
+ struct snd_kcontrol *master,
+ const char * const *list)
+{
+ for (; *list; list++) {
+ struct snd_kcontrol *follower = ctl_find(card, *list);
+ /* dev_dbg(card->dev, "add_followers - %s\n", *list); */
+ if (follower) {
+ /* dev_dbg(card->dev, "follower %s found\n", *list); */
+ snd_ctl_add_follower(master, follower);
+ }
+ }
+}
+
+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;
+ add_followers(ice->card, vmaster, follower_vols);
+ err = snd_ctl_add(ice->card, vmaster);
+ 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(sizeof(*spec), GFP_KERNEL);
+ 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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..b46df1821
--- /dev/null
+++ b/sound/pci/ice1712/maya44.c
@@ -0,0 +1,748 @@
+// 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 <tiwai@suse.de>
+ * Based on the patches by Rainer Zimmermann <mail@lightshed.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+
+#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;
+
+ mutex_lock(&chip->mutex);
+ ucontrol->value.integer.value[0] = wm->volumes[idx][0];
+ ucontrol->value.integer.value[1] = wm->volumes[idx][1];
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ for (i = 0; i < 2; i++)
+ wm8776_write_bits(ice, &chip->wm[i],
+ WM8776_REG_MASTER_MODE_CONTROL,
+ 0x180, val);
+ mutex_unlock(&chip->mutex);
+}
+
+/*
+ * 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(sizeof(*chip), GFP_KERNEL);
+ 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..1e47e46ab
--- /dev/null
+++ b/sound/pci/ice1712/phase.c
@@ -0,0 +1,950 @@
+// 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 <misha@epiphan.com>
+ */
+
+/* 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 <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "phase.h"
+#include <sound/tlv.h>
+
+/* 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ?
+ 0 : 1;
+ mutex_unlock(&ice->gpio_mutex);
+ 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(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ /* Initialize analog chips */
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&ice->gpio_mutex);
+ 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 <misha@epiphan.com>
+ */
+
+#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..683909ca1
--- /dev/null
+++ b/sound/pci/ice1712/pontis.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Pontis MS300
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.enumerated.item[0] = ice->gpio.saved[0];
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ ucontrol->value.integer.value[0] = (~ice->gpio.write_mask & 0xffff) | 0x00f0;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&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;
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ ucontrol->value.integer.value[0] = ice->gpio.direction & 0xff0f;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ val = ucontrol->value.integer.value[0] & 0xff0f;
+ changed = (val != ice->gpio.direction);
+ ice->gpio.direction = val;
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+ mutex_lock(&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;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&ice->gpio_mutex);
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if (reg <= 0x17 && val <= 0xffff)
+ wm_put(ice, reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (reg = 0; reg <= 0x17; reg++) {
+ val = wm_get(ice, reg);
+ snd_iprintf(buffer, "%02x = %04x\n", reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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 <tiwai@suse.de>
+ */
+
+#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..096ec76f5
--- /dev/null
+++ b/sound/pci/ice1712/prodigy192.c
@@ -0,0 +1,791 @@
+// 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 <tiwai@suse.de>
+ * Copyright (c) 2003 Dimitromanolakis Apostolos <apostol@cs.utoronto.ca>
+ * Copyright (c) 2004 Kouichi ONO <co2b@ceres.dti.ne.jp>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "prodigy192.h"
+#include "stac946x.h"
+#include <sound/tlv.h>
+
+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, change;
+
+ 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 */
+ mutex_lock(&spec->mute_mutex);
+ /*
+ dev_dbg(ice->card->dev, "Mute put: reg 0x%02x, ctrl value: 0x%02x\n", idx,
+ ucontrol->value.integer.value[0]);
+ */
+ change = stac9460_dac_mute(ice, idx, ucontrol->value.integer.value[0]);
+ mutex_unlock(&spec->mute_mutex);
+ 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;
+ 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) {
+ ovol = (0x7f - nvol) | (tmp & 0x80);
+ /*
+ dev_dbg(ice->card->dev, "DAC Volume: reg 0x%02x: 0x%02x\n",
+ idx, ovol);
+ */
+ 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 */
+ mutex_lock(&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);
+ }
+ mutex_unlock(&spec->mute_mutex);
+}
+
+
+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(sizeof(*spec), GFP_KERNEL);
+ 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..9aa12a67d
--- /dev/null
+++ b/sound/pci/ice1712/prodigy_hifi.c
@@ -0,0 +1,1265 @@
+// 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 <julian@jusst.de>
+ * Copyright (c) 2007 allank
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.enumerated.item[0] = wm_get(ice, WM_ADC_MUX) & 0x1f;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] =
+ (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] =
+ (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] =
+ (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90;
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ 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;
+ mutex_lock(&ice->gpio_mutex);
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if (reg <= 0x17 && val <= 0xffff)
+ wm_put(ice, reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (reg = 0; reg <= 0x17; reg++) {
+ val = wm_get(ice, reg);
+ snd_iprintf(buffer, "%02x = %04x\n", reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&ice->gpio_mutex);
+ 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ice->akm)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ 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;
+ mutex_lock(&ice->gpio_mutex);
+ ak4396_init(ice);
+ for (i = 0; i < 2; i++)
+ ak4396_write(ice, AK4396_LCH_ATT + i, spec->vol[i] & 0xff);
+ mutex_unlock(&ice->gpio_mutex);
+ 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(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ice->akm)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ 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 <tiwai@suse.de>
+ */
+
+#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..7aa3f9204
--- /dev/null
+++ b/sound/pci/ice1712/psc724.c
@@ -0,0 +1,450 @@
+// 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 <linux@rainbow-software.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#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_ctl_elem_id elem_id;
+ 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 */
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strlcpy(elem_id.name, "Master Speakers Playback Switch",
+ sizeof(elem_id.name));
+ kctl = snd_ctl_find_id(ice->card, &elem_id);
+ snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+ /* and headphone mute change */
+ strlcpy(elem_id.name, spec->wm8776.ctl[WM8776_CTL_HP_SW].name,
+ sizeof(elem_id.name));
+ kctl = snd_ctl_find_id(ice->card, &elem_id);
+ 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(sizeof(*spec), GFP_KERNEL);
+ 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..0e3e04aa9
--- /dev/null
+++ b/sound/pci/ice1712/quartet.c
@@ -0,0 +1,1088 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Infrasonic Quartet
+ *
+ * Copyright (c) 2009 Pavel Hofman <pavel.hofman@ivitera.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/info.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include <sound/ak4113.h>
+#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;
+
+ mutex_lock(&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);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+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 = new = 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 struct snd_kcontrol *ctl_find(struct snd_card *card,
+ const char *name)
+{
+ struct snd_ctl_elem_id sid = {0};
+
+ strlcpy(sid.name, name, sizeof(sid.name));
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_find_id(card, &sid);
+}
+
+static void add_followers(struct snd_card *card,
+ struct snd_kcontrol *master, const char * const *list)
+{
+ for (; *list; list++) {
+ struct snd_kcontrol *follower = ctl_find(card, *list);
+ if (follower)
+ snd_ctl_add_follower(master, follower);
+ }
+}
+
+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;
+ add_followers(ice->card, vmaster, follower_vols);
+ err = snd_ctl_add(ice->card, vmaster);
+ 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(sizeof(*spec), GFP_KERNEL);
+ 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 = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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..bcf114152
--- /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 <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#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(sizeof(*spec), GFP_KERNEL);
+ 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(sizeof(*spec), GFP_KERNEL);
+ 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 = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ 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 <tiwai@suse.de>
+ */
+
+#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..ffa9d8860
--- /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 <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+
+#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(sizeof(*spec), GFP_KERNEL);
+ 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 <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#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 <tiwai@suse.de>
+ */
+
+#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 <linux@rainbow-software.org>
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#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 <linux@rainbow-software.org>
+ */
+
+#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..d96008df8
--- /dev/null
+++ b/sound/pci/ice1712/wm8776.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT17xx
+ *
+ * Lowlevel functions for WM8776 codec
+ *
+ * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#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;
+ struct snd_ctl_elem_id elem_id;
+ unsigned int index_offset;
+
+ memset(&elem_id, 0, sizeof(elem_id));
+ strlcpy(elem_id.name, ctl_name, sizeof(elem_id.name));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ kctl = snd_ctl_find_id(card, &elem_id);
+ 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 <linux@rainbow-software.org>
+ */
+
+#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..f613f0067
--- /dev/null
+++ b/sound/pci/ice1712/wtm.c
@@ -0,0 +1,632 @@
+// 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 <klem.dev@gmail.com>
+ * Some functions are taken from the Prodigy192 driver
+ * source
+ */
+
+
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <linux/slab.h>
+
+#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;
+
+ mutex_lock(&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;
+
+ mutex_unlock(&spec->mute_mutex);
+ 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 */
+ mutex_lock(&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);
+ mutex_unlock(&spec->mute_mutex);
+}
+
+
+/*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(sizeof(*spec), GFP_KERNEL);
+ 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..3707dc273
--- /dev/null
+++ b/sound/pci/intel8x0.c
@@ -0,0 +1,3266 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for Intel ICH (i8x0) chipsets
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ * This code also contains alpha support for SiS 735 chipsets provided
+ * by Mike Pieper <mptei@users.sourceforge.net>. We have no datasheet
+ * for SiS735, so the code is not fully functional.
+ *
+
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; SiS 7012; Ali 5455");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel,82801AA-ICH},"
+ "{Intel,82901AB-ICH0},"
+ "{Intel,82801BA-ICH2},"
+ "{Intel,82801CA-ICH3},"
+ "{Intel,82801DB-ICH4},"
+ "{Intel,ICH5},"
+ "{Intel,ICH6},"
+ "{Intel,ICH7},"
+ "{Intel,6300ESB},"
+ "{Intel,ESB2},"
+ "{Intel,MX440},"
+ "{SiS,SI7012},"
+ "{NVidia,nForce Audio},"
+ "{NVidia,nForce2 Audio},"
+ "{NVidia,nForce3 Audio},"
+ "{NVidia,MCP04},"
+ "{NVidia,MCP501},"
+ "{NVidia,CK804},"
+ "{NVidia,CK8},"
+ "{NVidia,CK8S},"
+ "{AMD,AMD768},"
+ "{AMD,AMD8111},"
+ "{ALI,M5455}}");
+
+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[] = {
+ { PCI_VDEVICE(INTEL, 0x2415), DEVICE_INTEL }, /* 82801AA */
+ { PCI_VDEVICE(INTEL, 0x2425), DEVICE_INTEL }, /* 82901AB */
+ { PCI_VDEVICE(INTEL, 0x2445), DEVICE_INTEL }, /* 82801BA */
+ { PCI_VDEVICE(INTEL, 0x2485), DEVICE_INTEL }, /* ICH3 */
+ { PCI_VDEVICE(INTEL, 0x24c5), DEVICE_INTEL_ICH4 }, /* ICH4 */
+ { PCI_VDEVICE(INTEL, 0x24d5), DEVICE_INTEL_ICH4 }, /* ICH5 */
+ { PCI_VDEVICE(INTEL, 0x25a6), DEVICE_INTEL_ICH4 }, /* ESB */
+ { PCI_VDEVICE(INTEL, 0x266e), DEVICE_INTEL_ICH4 }, /* ICH6 */
+ { PCI_VDEVICE(INTEL, 0x27de), DEVICE_INTEL_ICH4 }, /* ICH7 */
+ { PCI_VDEVICE(INTEL, 0x2698), DEVICE_INTEL_ICH4 }, /* ESB2 */
+ { PCI_VDEVICE(INTEL, 0x7195), DEVICE_INTEL }, /* 440MX */
+ { PCI_VDEVICE(SI, 0x7012), DEVICE_SIS }, /* SI7012 */
+ { PCI_VDEVICE(NVIDIA, 0x01b1), DEVICE_NFORCE }, /* NFORCE */
+ { PCI_VDEVICE(NVIDIA, 0x003a), DEVICE_NFORCE }, /* MCP04 */
+ { PCI_VDEVICE(NVIDIA, 0x006a), DEVICE_NFORCE }, /* NFORCE2 */
+ { PCI_VDEVICE(NVIDIA, 0x0059), DEVICE_NFORCE }, /* CK804 */
+ { PCI_VDEVICE(NVIDIA, 0x008a), DEVICE_NFORCE }, /* CK8 */
+ { PCI_VDEVICE(NVIDIA, 0x00da), DEVICE_NFORCE }, /* NFORCE3 */
+ { PCI_VDEVICE(NVIDIA, 0x00ea), DEVICE_NFORCE }, /* CK8S */
+ { PCI_VDEVICE(NVIDIA, 0x026b), DEVICE_NFORCE }, /* MCP51 */
+ { PCI_VDEVICE(AMD, 0x746d), DEVICE_INTEL }, /* AMD8111 */
+ { PCI_VDEVICE(AMD, 0x7445), DEVICE_INTEL }, /* AMD768 */
+ { PCI_VDEVICE(AL, 0x5455), DEVICE_ALI }, /* Ali5455 */
+ { 0, }
+};
+
+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);
+ if ((tmp = igetdword(chip, ICHREG(GLOB_STA))) & 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);
+ if ((tmp = igetdword(chip, ICHREG(GLOB_STA))) & 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;
+ unsigned long flags;
+ int status, civ, i, step;
+ int ack = 0;
+
+ if (!(ichdev->prepared || chip->in_measurement) || ichdev->suspended)
+ return;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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) {
+ // snd_printd("civ same %d\n", 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;
+ // if (step != 1)
+ // snd_printd("step = %d, %d -> %d\n", step, ichdev->civ, civ);
+ 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;
+ }
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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;
+
+ spin_lock_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;
+ }
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+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;
+
+ spin_lock(&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;
+ spin_unlock(&chip->reg_lock);
+ 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;
+ }
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 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;
+
+ spin_lock_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 */
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ val = igetdword(chip, ICHREG(ALI_INTERFACECR));
+ val &= ~ICH_ALI_IF_AC97SP;
+ iputdword(chip, ICHREG(ALI_INTERFACECR), val);
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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_UC : 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
+ strcpy(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
+ strcpy(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);
+ }
+ }
+ if ((err = snd_ac97_bus(chip->card, 0, ops, chip, &pbus)) < 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;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i])) < 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[1]->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) {
+ if ((err = snd_intel8x0_ich_chip_init(chip, probing)) < 0)
+ return err;
+ iagetword(chip, 0); /* clear semaphore flag */
+ } else {
+ if ((err = snd_intel8x0_ali_chip_init(chip, probing)) < 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 int snd_intel8x0_free(struct intel8x0 *chip)
+{
+ 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);
+ if (chip->bdbars.area)
+ snd_dma_free_pages(&chip->bdbars);
+ if (chip->addr)
+ pci_iounmap(chip->pci, chip->addr);
+ if (chip->bmaddr)
+ pci_iounmap(chip->pci, chip->bmaddr);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(intel8x0_pm, intel8x0_suspend, intel8x0_resume);
+#define INTEL8X0_PM_OPS &intel8x0_pm
+#else
+#define INTEL8X0_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+#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 */
+
+ __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;
+ spin_lock_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();
+ spin_unlock_irq(&chip->reg_lock);
+ msleep(50);
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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);
+}
+
+static int snd_intel8x0_dev_free(struct snd_device *device)
+{
+ struct intel8x0 *chip = device->device_data;
+ return snd_intel8x0_free(chip);
+}
+
+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_create(struct snd_card *card,
+ struct pci_dev *pci,
+ unsigned long device_type,
+ struct intel8x0 **r_intel8x0)
+{
+ struct intel8x0 *chip;
+ int err;
+ unsigned int i;
+ unsigned int int_sta_masks;
+ struct ichdev *ichdev;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_intel8x0_dev_free,
+ };
+
+ 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;
+
+ *r_intel8x0 = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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 */
+
+ if ((err = pci_request_regions(pci, card->shortname)) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+
+ if (device_type == DEVICE_ALI) {
+ /* ALI5455 has no ac97 region */
+ chip->bmaddr = pci_iomap(pci, 0, 0);
+ goto port_inited;
+ }
+
+ if (pci_resource_flags(pci, 2) & IORESOURCE_MEM) /* ICH4 and Nforce */
+ chip->addr = pci_iomap(pci, 2, 0);
+ else
+ chip->addr = pci_iomap(pci, 0, 0);
+ if (!chip->addr) {
+ dev_err(card->dev, "AC'97 space ioremap problem\n");
+ snd_intel8x0_free(chip);
+ return -EIO;
+ }
+ if (pci_resource_flags(pci, 3) & IORESOURCE_MEM) /* ICH4 */
+ chip->bmaddr = pci_iomap(pci, 3, 0);
+ else
+ chip->bmaddr = pci_iomap(pci, 1, 0);
+
+ port_inited:
+ if (!chip->bmaddr) {
+ dev_err(card->dev, "Controller space ioremap problem\n");
+ snd_intel8x0_free(chip);
+ return -EIO;
+ }
+ 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 */
+ if (snd_dma_alloc_pages(intel8x0_dma_type(chip), &pci->dev,
+ chip->bdbars_count * sizeof(u32) * ICH_MAX_FRAGS * 2,
+ &chip->bdbars) < 0) {
+ snd_intel8x0_free(chip);
+ dev_err(card->dev, "cannot allocate buffer descriptors\n");
+ 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];
+
+ if ((err = snd_intel8x0_chip_init(chip, 1)) < 0) {
+ snd_intel8x0_free(chip);
+ return err;
+ }
+
+ /* request irq after initializaing int_sta_mask, etc */
+ 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);
+ snd_intel8x0_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_intel8x0_free(chip);
+ return err;
+ }
+
+ *r_intel8x0 = chip;
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ if (spdif_aclink < 0)
+ spdif_aclink = check_default_spdif_aclink(pci);
+
+ strcpy(card->driver, "ICH");
+ if (!spdif_aclink) {
+ switch (pci_id->driver_data) {
+ case DEVICE_NFORCE:
+ strcpy(card->driver, "NFORCE");
+ break;
+ case DEVICE_INTEL_ICH4:
+ strcpy(card->driver, "ICH4");
+ }
+ }
+
+ strcpy(card->shortname, "Intel ICH");
+ for (name = shortnames; name->id; name++) {
+ if (pci->device == name->id) {
+ strcpy(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;
+ }
+
+ if ((err = snd_intel8x0_create(card, pci, pci_id->driver_data,
+ &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ if ((err = snd_intel8x0_mixer(chip, ac97_clock, ac97_quirk)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_intel8x0_pcm(chip)) < 0) {
+ snd_card_free(card);
+ 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);
+ }
+ }
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ return 0;
+}
+
+static void snd_intel8x0_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver intel8x0_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_intel8x0_ids,
+ .probe = snd_intel8x0_probe,
+ .remove = snd_intel8x0_remove,
+ .driver = {
+ .pm = INTEL8X0_PM_OPS,
+ },
+};
+
+module_pci_driver(intel8x0_driver);
diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c
new file mode 100644
index 000000000..1b7df0c4e
--- /dev/null
+++ b/sound/pci/intel8x0m.c
@@ -0,0 +1,1298 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA modem driver for Intel ICH (i8x0) chipsets
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ * This is modified (by Sasha Khapyorsky <sashak@alsa-project.org>) version
+ * of ALSA ICH sound driver intel8x0.c .
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; "
+ "SiS 7013; NVidia MCP/2/2S/3 modems");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel,82801AA-ICH},"
+ "{Intel,82901AB-ICH0},"
+ "{Intel,82801BA-ICH2},"
+ "{Intel,82801CA-ICH3},"
+ "{Intel,82801DB-ICH4},"
+ "{Intel,ICH5},"
+ "{Intel,ICH6},"
+ "{Intel,ICH7},"
+ "{Intel,MX440},"
+ "{SiS,7013},"
+ "{NVidia,NForce Modem},"
+ "{NVidia,NForce2 Modem},"
+ "{NVidia,NForce2s Modem},"
+ "{NVidia,NForce3 Modem},"
+ "{AMD,AMD768}}");
+
+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[] = {
+ { PCI_VDEVICE(INTEL, 0x2416), DEVICE_INTEL }, /* 82801AA */
+ { PCI_VDEVICE(INTEL, 0x2426), DEVICE_INTEL }, /* 82901AB */
+ { PCI_VDEVICE(INTEL, 0x2446), DEVICE_INTEL }, /* 82801BA */
+ { PCI_VDEVICE(INTEL, 0x2486), DEVICE_INTEL }, /* ICH3 */
+ { PCI_VDEVICE(INTEL, 0x24c6), DEVICE_INTEL }, /* ICH4 */
+ { PCI_VDEVICE(INTEL, 0x24d6), DEVICE_INTEL }, /* ICH5 */
+ { PCI_VDEVICE(INTEL, 0x266d), DEVICE_INTEL }, /* ICH6 */
+ { PCI_VDEVICE(INTEL, 0x27dd), DEVICE_INTEL }, /* ICH7 */
+ { PCI_VDEVICE(INTEL, 0x7196), DEVICE_INTEL }, /* 440MX */
+ { PCI_VDEVICE(AMD, 0x7446), DEVICE_INTEL }, /* AMD768 */
+ { PCI_VDEVICE(SI, 0x7013), DEVICE_SIS }, /* SI7013 */
+ { PCI_VDEVICE(NVIDIA, 0x01c1), DEVICE_NFORCE }, /* NFORCE */
+ { PCI_VDEVICE(NVIDIA, 0x0069), DEVICE_NFORCE }, /* NFORCE2 */
+ { PCI_VDEVICE(NVIDIA, 0x0089), DEVICE_NFORCE }, /* NFORCE2s */
+ { PCI_VDEVICE(NVIDIA, 0x00d9), DEVICE_NFORCE }, /* NFORCE3 */
+ { PCI_VDEVICE(AMD, 0x746e), DEVICE_INTEL }, /* AMD8111 */
+#if 0
+ { PCI_VDEVICE(AL, 0x5455), DEVICE_ALI }, /* Ali5455 */
+#endif
+ { 0, }
+};
+
+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);
+ if ((tmp = igetdword(chip, ICHREG(GLOB_STA))) & 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) {
+ // snd_printd("civ same %d\n", 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;
+ // if (step != 1)
+ // snd_printd("step = %d, %d -> %d\n", step, ichdev->civ, civ);
+ 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;
+
+ spin_lock(&chip->reg_lock);
+ status = igetdword(chip, chip->int_sta_reg);
+ if (status == 0xffffffff) { /* we are not yet resumed */
+ spin_unlock(&chip->reg_lock);
+ return IRQ_NONE;
+ }
+ if ((status & chip->int_sta_mask) == 0) {
+ if (status)
+ iputdword(chip, chip->int_sta_reg, status);
+ spin_unlock(&chip->reg_lock);
+ 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);
+ spin_unlock(&chip->reg_lock);
+
+ 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
+ strcpy(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
+ strcpy(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));
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 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;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &x97)) < 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 SIS7012 */
+ 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;
+
+ if ((err = snd_intel8x0m_ich_chip_init(chip, probing)) < 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 int snd_intel8x0m_free(struct intel8x0m *chip)
+{
+ 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);
+ if (chip->bdbars.area)
+ snd_dma_free_pages(&chip->bdbars);
+ if (chip->addr)
+ pci_iounmap(chip->pci, chip->addr);
+ if (chip->bmaddr)
+ pci_iounmap(chip->pci, chip->bmaddr);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(intel8x0m_pm, intel8x0m_suspend, intel8x0m_resume);
+#define INTEL8X0M_PM_OPS &intel8x0m_pm
+#else
+#define INTEL8X0M_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+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);
+}
+
+static int snd_intel8x0m_dev_free(struct snd_device *device)
+{
+ struct intel8x0m *chip = device->device_data;
+ return snd_intel8x0m_free(chip);
+}
+
+struct ich_reg_info {
+ unsigned int int_sta_mask;
+ unsigned int offset;
+};
+
+static int snd_intel8x0m_create(struct snd_card *card,
+ struct pci_dev *pci,
+ unsigned long device_type,
+ struct intel8x0m **r_intel8x0m)
+{
+ struct intel8x0m *chip;
+ int err;
+ unsigned int i;
+ unsigned int int_sta_masks;
+ struct ichdev *ichdev;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_intel8x0m_dev_free,
+ };
+ static const struct ich_reg_info intel_regs[2] = {
+ { ICH_MIINT, 0 },
+ { ICH_MOINT, 0x10 },
+ };
+ const struct ich_reg_info *tbl;
+
+ *r_intel8x0m = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ spin_lock_init(&chip->reg_lock);
+ chip->device_type = device_type;
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ if ((err = pci_request_regions(pci, card->shortname)) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+
+ if (device_type == DEVICE_ALI) {
+ /* ALI5455 has no ac97 region */
+ chip->bmaddr = pci_iomap(pci, 0, 0);
+ goto port_inited;
+ }
+
+ if (pci_resource_flags(pci, 2) & IORESOURCE_MEM) /* ICH4 and Nforce */
+ chip->addr = pci_iomap(pci, 2, 0);
+ else
+ chip->addr = pci_iomap(pci, 0, 0);
+ if (!chip->addr) {
+ dev_err(card->dev, "AC'97 space ioremap problem\n");
+ snd_intel8x0m_free(chip);
+ return -EIO;
+ }
+ if (pci_resource_flags(pci, 3) & IORESOURCE_MEM) /* ICH4 */
+ chip->bmaddr = pci_iomap(pci, 3, 0);
+ else
+ chip->bmaddr = pci_iomap(pci, 1, 0);
+ if (!chip->bmaddr) {
+ dev_err(card->dev, "Controller space ioremap problem\n");
+ snd_intel8x0m_free(chip);
+ return -EIO;
+ }
+
+ port_inited:
+ /* 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 */
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ chip->bdbars_count * sizeof(u32) * ICH_MAX_FRAGS * 2,
+ &chip->bdbars) < 0) {
+ snd_intel8x0m_free(chip);
+ 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);
+
+ if ((err = snd_intel8x0m_chip_init(chip, 1)) < 0) {
+ snd_intel8x0m_free(chip);
+ return err;
+ }
+
+ 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);
+ snd_intel8x0m_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_intel8x0m_free(chip);
+ return err;
+ }
+
+ *r_intel8x0m = chip;
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, "ICH-MODEM");
+ strcpy(card->shortname, "Intel ICH");
+ for (name = shortnames; name->id; name++) {
+ if (pci->device == name->id) {
+ strcpy(card->shortname, name->s);
+ break;
+ }
+ }
+ strcat(card->shortname," Modem");
+
+ if ((err = snd_intel8x0m_create(card, pci, pci_id->driver_data, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ if ((err = snd_intel8x0m_mixer(chip, ac97_clock)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_intel8x0m_pcm(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_intel8x0m_proc_init(chip);
+
+ sprintf(card->longname, "%s at irq %i",
+ card->shortname, chip->irq);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ return 0;
+}
+
+static void snd_intel8x0m_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver intel8x0m_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_intel8x0m_ids,
+ .probe = snd_intel8x0m_probe,
+ .remove = snd_intel8x0m_remove,
+ .driver = {
+ .pm = INTEL8X0M_PM_OPS,
+ },
+};
+
+module_pci_driver(intel8x0m_driver);
diff --git a/sound/pci/korg1212/Makefile b/sound/pci/korg1212/Makefile
new file mode 100644
index 000000000..42eb287c7
--- /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 <perex@perex.cz>
+#
+
+snd-korg1212-objs := 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..2eddd9de9
--- /dev/null
+++ b/sound/pci/korg1212/korg1212.c
@@ -0,0 +1,2473 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for the Korg 1212 IO PCI card
+ *
+ * Copyright (c) 2001 Haroldo Gamal <gamal@alternex.com.br>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+
+// ----------------------------------------------------------------------------
+// Debug Stuff
+// ----------------------------------------------------------------------------
+#define K1212_DEBUG_LEVEL 0
+#if K1212_DEBUG_LEVEL > 0
+#define K1212_DEBUG_PRINTK(fmt,args...) printk(KERN_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...) printk(KERN_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;
+
+ struct timer_list timer; /* timer callback for checking ack of stop request */
+ int stop_pending_cnt; /* counter for stop pending check */
+
+ 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_is_processed;
+
+};
+
+MODULE_DESCRIPTION("korg1212");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{KORG,korg1212}}");
+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 <gamal@alternex.com.br>");
+
+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)
+{
+ if (! korg1212->stop_pending_cnt) {
+ korg1212->sharedBufferPtr->cardCommand = 0xffffffff;
+ /* program the timer */
+ korg1212->stop_pending_cnt = HZ;
+ mod_timer(&korg1212->timer, jiffies + 1);
+ }
+}
+
+static void snd_korg1212_SendStopAndWait(struct snd_korg1212 *korg1212)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&korg1212->lock, flags);
+ korg1212->dsp_stop_is_processed = 0;
+ snd_korg1212_SendStop(korg1212);
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+ wait_event_timeout(korg1212->wait, korg1212->dsp_stop_is_processed, (HZ * 3) / 2);
+}
+
+/* timer callback for checking the ack of stop request */
+static void snd_korg1212_timer_func(struct timer_list *t)
+{
+ struct snd_korg1212 *korg1212 = from_timer(korg1212, t, timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+ if (korg1212->sharedBufferPtr->cardCommand == 0) {
+ /* ack'ed */
+ korg1212->stop_pending_cnt = 0;
+ korg1212->dsp_stop_is_processed = 1;
+ wake_up(&korg1212->wait);
+ K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: Stop ack'ed [%s]\n",
+ stateName[korg1212->cardState]);
+ } else {
+ if (--korg1212->stop_pending_cnt > 0) {
+ /* reprogram timer */
+ mod_timer(&korg1212->timer, jiffies + 1);
+ } else {
+ snd_printd("korg1212_timer_func timeout\n");
+ korg1212->sharedBufferPtr->cardCommand = 0;
+ korg1212->dsp_stop_is_processed = 1;
+ wake_up(&korg1212->wait);
+ K1212_DEBUG_PRINTK("K1212_DEBUG: Stop timeout [%s]\n",
+ stateName[korg1212->cardState]);
+ }
+ }
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+}
+
+static int snd_korg1212_TurnOnIdleMonitor(struct snd_korg1212 *korg1212)
+{
+ unsigned long flags;
+ int rc;
+
+ udelay(INTERCOMMAND_DELAY);
+ spin_lock_irqsave(&korg1212->lock, flags);
+ korg1212->idleMonitorOn = 1;
+ rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode,
+ K1212_MODE_MonitorOn, 0, 0, 0);
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+ return rc;
+}
+
+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);
+ mutex_lock(&korg1212->open_mutex);
+ if (korg1212->opencnt++ == 0) {
+ snd_korg1212_TurnOffIdleMonitor(korg1212);
+ snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN);
+ }
+
+ mutex_unlock(&korg1212->open_mutex);
+ 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);
+
+ mutex_lock(&korg1212->open_mutex);
+ if (--(korg1212->opencnt)) {
+ mutex_unlock(&korg1212->open_mutex);
+ 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) {
+ mutex_unlock(&korg1212->open_mutex);
+ 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);
+ }
+
+ mutex_unlock(&korg1212->open_mutex);
+ 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;
+ unsigned long flags;
+
+ 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;
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+
+ // ----------------------------------------------------------------------------
+ // 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]);
+ }
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+
+ 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;
+
+ spin_lock(&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]);
+ snd_printk(KERN_ERR "korg1212: DMA Error\n");
+ korg1212->errorcnt++;
+ korg1212->totalerrorcnt++;
+ korg1212->sharedBufferPtr->cardCommand = 0;
+ snd_korg1212_setCardState(korg1212, K1212_STATE_ERRORSTOP);
+ 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;
+ 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--;
+
+ spin_unlock(&korg1212->lock);
+
+ 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 ) {
+ printk(KERN_DEBUG "K1212_DEBUG: snd_korg1212_silence KERNEL EFAULT dst=%p iter=%d\n",
+ 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,
+ void __user *dst, int pos, int count,
+ bool in_kernel)
+{
+ 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 ) {
+ printk(KERN_DEBUG "K1212_DEBUG: snd_korg1212_copy_to KERNEL EFAULT, src=%p dst=%p iter=%d\n", src, dst, i);
+ return -EFAULT;
+ }
+#endif
+ if (in_kernel)
+ memcpy((__force void *)dst, src, size);
+ else if (copy_to_user(dst, src, size))
+ return -EFAULT;
+ src++;
+ dst += size;
+ }
+
+ return 0;
+}
+
+static int snd_korg1212_copy_from(struct snd_pcm_substream *substream,
+ void __user *src, int pos, int count,
+ bool in_kernel)
+{
+ 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: snd_korg1212_copy_from 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 *) dst < (void *) korg1212->playDataBufsPtr ||
+ (void *) dst > (void *) korg1212->playDataBufsPtr[8].bufferData ) {
+ printk(KERN_DEBUG "K1212_DEBUG: snd_korg1212_copy_from KERNEL EFAULT, src=%p dst=%p iter=%d\n", src, dst, i);
+ return -EFAULT;
+ }
+#endif
+ if (in_kernel)
+ memcpy(dst, (__force void *)src, size);
+ else if (copy_from_user(dst, src, size))
+ return -EFAULT;
+ dst++;
+ src += size;
+ }
+
+ 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)
+{
+ unsigned long flags;
+ 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);
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+
+ korg1212->playback_substream = substream;
+ korg1212->playback_pid = current->pid;
+ korg1212->periodsize = K1212_PERIODS;
+ korg1212->channels = K1212_CHANNELS;
+ korg1212->errorcnt = 0;
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+
+ 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)
+{
+ unsigned long flags;
+ 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);
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+
+ korg1212->capture_substream = substream;
+ korg1212->capture_pid = current->pid;
+ korg1212->periodsize = K1212_PERIODS;
+ korg1212->channels = K1212_CHANNELS;
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+
+ 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)
+{
+ unsigned long flags;
+ 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);
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+
+ korg1212->playback_pid = -1;
+ korg1212->playback_substream = NULL;
+ korg1212->periodsize = 0;
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+
+ snd_korg1212_CloseCard(korg1212);
+ return 0;
+}
+
+static int snd_korg1212_capture_close(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+
+ K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_capture_close [%s]\n",
+ stateName[korg1212->cardState]);
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+
+ korg1212->capture_pid = -1;
+ korg1212->capture_substream = NULL;
+ korg1212->periodsize = 0;
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+
+ 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)
+{
+ unsigned long flags;
+ 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]);
+
+ spin_lock_irqsave(&korg1212->lock, flags);
+
+ 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) {
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+ return -EBUSY;
+ }
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+ return 0;
+ }
+
+ if ((err = snd_korg1212_SetRate(korg1212, params_rate(params))) < 0) {
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+ return err;
+ }
+
+ korg1212->channels = params_channels(params);
+ korg1212->periodsize = K1212_PERIOD_BYTES;
+
+ spin_unlock_irqrestore(&korg1212->lock, flags);
+
+ 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]);
+
+ spin_lock_irq(&korg1212->lock);
+
+ /* FIXME: we should wait for ack! */
+ if (korg1212->stop_pending_cnt > 0) {
+ K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_prepare - Stop is pending... [%s]\n",
+ stateName[korg1212->cardState]);
+ spin_unlock_irq(&korg1212->lock);
+ return -EAGAIN;
+ /*
+ korg1212->sharedBufferPtr->cardCommand = 0;
+ del_timer(&korg1212->timer);
+ korg1212->stop_pending_cnt = 0;
+ */
+ }
+
+ rc = snd_korg1212_SetupForPlay(korg1212);
+
+ korg1212->currentBuffer = 0;
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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);
+
+ spin_lock(&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;
+ }
+ spin_unlock(&korg1212->lock);
+ 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,
+ void __user *src, unsigned long count)
+{
+ return snd_korg1212_copy_from(substream, src, pos, count, false);
+}
+
+static int snd_korg1212_playback_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *src, unsigned long count)
+{
+ return snd_korg1212_copy_from(substream, (void __user *)src,
+ pos, count, true);
+}
+
+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,
+ void __user *dst, unsigned long count)
+{
+ return snd_korg1212_copy_to(substream, dst, pos, count, false);
+}
+
+static int snd_korg1212_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *dst, unsigned long count)
+{
+ return snd_korg1212_copy_to(substream, (void __user *)dst,
+ pos, count, true);
+}
+
+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,
+ .pointer = snd_korg1212_playback_pointer,
+ .copy_user = snd_korg1212_playback_copy,
+ .copy_kernel = snd_korg1212_playback_copy_kernel,
+ .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,
+ .pointer = snd_korg1212_capture_pointer,
+ .copy_user = snd_korg1212_capture_copy,
+ .copy_kernel = snd_korg1212_capture_copy_kernel,
+};
+
+/*
+ * 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;
+
+ spin_lock_irq(&korg1212->lock);
+
+ u->value.integer.value[0] = korg1212->volumePhase[i];
+
+ if (i >= 8)
+ u->value.integer.value[1] = korg1212->volumePhase[i+1];
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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;
+
+ spin_lock_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;
+ }
+ }
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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;
+
+ spin_lock_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]);
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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;
+
+ spin_lock_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;
+ }
+ }
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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;
+
+ spin_lock_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];
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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;
+
+ spin_lock_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;
+ }
+ }
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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);
+
+ spin_lock_irq(&korg1212->lock);
+
+ u->value.integer.value[0] = korg1212->leftADCInSens;
+ u->value.integer.value[1] = korg1212->rightADCInSens;
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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;
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&korg1212->lock);
+
+ 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);
+
+ spin_lock_irq(&korg1212->lock);
+
+ ucontrol->value.enumerated.item[0] = korg1212->clkSource;
+
+ spin_unlock_irq(&korg1212->lock);
+ 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;
+ spin_lock_irq(&korg1212->lock);
+ change = val != korg1212->clkSource;
+ snd_korg1212_SetClockSource(korg1212, val);
+ spin_unlock_irq(&korg1212->lock);
+ 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<kAudioChannels; n++)
+ snd_iprintf(buffer, " Channel %d: %s -> %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 int
+snd_korg1212_free(struct snd_korg1212 *korg1212)
+{
+ snd_korg1212_TurnOffIdleMonitor(korg1212);
+
+ if (korg1212->irq >= 0) {
+ snd_korg1212_DisableCardInterrupts(korg1212);
+ free_irq(korg1212->irq, korg1212);
+ korg1212->irq = -1;
+ }
+
+ if (korg1212->iobase != NULL) {
+ iounmap(korg1212->iobase);
+ korg1212->iobase = NULL;
+ }
+
+ pci_release_regions(korg1212->pci);
+
+ // ----------------------------------------------------
+ // free up memory resources used for the DSP download.
+ // ----------------------------------------------------
+ if (korg1212->dma_dsp.area) {
+ snd_dma_free_pages(&korg1212->dma_dsp);
+ korg1212->dma_dsp.area = NULL;
+ }
+
+#ifndef K1212_LARGEALLOC
+
+ // ------------------------------------------------------
+ // free up memory resources used for the Play/Rec Buffers
+ // ------------------------------------------------------
+ if (korg1212->dma_play.area) {
+ snd_dma_free_pages(&korg1212->dma_play);
+ korg1212->dma_play.area = NULL;
+ }
+
+ if (korg1212->dma_rec.area) {
+ snd_dma_free_pages(&korg1212->dma_rec);
+ korg1212->dma_rec.area = NULL;
+ }
+
+#endif
+
+ // ----------------------------------------------------
+ // free up memory resources used for the Shared Buffers
+ // ----------------------------------------------------
+ if (korg1212->dma_shared.area) {
+ snd_dma_free_pages(&korg1212->dma_shared);
+ korg1212->dma_shared.area = NULL;
+ }
+
+ pci_disable_device(korg1212->pci);
+ kfree(korg1212);
+ return 0;
+}
+
+static int snd_korg1212_dev_free(struct snd_device *device)
+{
+ struct snd_korg1212 *korg1212 = device->device_data;
+ K1212_DEBUG_PRINTK("K1212_DEBUG: Freeing device\n");
+ return snd_korg1212_free(korg1212);
+}
+
+static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci,
+ struct snd_korg1212 **rchip)
+
+{
+ int err, rc;
+ unsigned int i;
+ unsigned iomem_size;
+ __maybe_unused unsigned ioport_size;
+ __maybe_unused unsigned iomem2_size;
+ struct snd_korg1212 * korg1212;
+ const struct firmware *dsp_code;
+
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_korg1212_dev_free,
+ };
+
+ * rchip = NULL;
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ korg1212 = kzalloc(sizeof(*korg1212), GFP_KERNEL);
+ if (korg1212 == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ korg1212->card = card;
+ korg1212->pci = pci;
+
+ init_waitqueue_head(&korg1212->wait);
+ spin_lock_init(&korg1212->lock);
+ mutex_init(&korg1212->open_mutex);
+ timer_setup(&korg1212->timer, snd_korg1212_timer_func, 0);
+
+ 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; i<kAudioChannels; i++)
+ korg1212->volumePhase[i] = 0;
+
+ if ((err = pci_request_regions(pci, "korg1212")) < 0) {
+ kfree(korg1212);
+ pci_disable_device(pci);
+ 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]);
+
+ if ((korg1212->iobase = ioremap(korg1212->iomem, iomem_size)) == NULL) {
+ snd_printk(KERN_ERR "korg1212: unable to remap memory region 0x%lx-0x%lx\n", korg1212->iomem,
+ korg1212->iomem + iomem_size - 1);
+ snd_korg1212_free(korg1212);
+ return -EBUSY;
+ }
+
+ err = request_irq(pci->irq, snd_korg1212_interrupt,
+ IRQF_SHARED,
+ KBUILD_MODNAME, korg1212);
+
+ if (err) {
+ snd_printk(KERN_ERR "korg1212: unable to grab IRQ %d\n", pci->irq);
+ snd_korg1212_free(korg1212);
+ return -EBUSY;
+ }
+
+ korg1212->irq = pci->irq;
+ card->sync_irq = korg1212->irq;
+
+ 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]);
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ sizeof(struct KorgSharedBuffer), &korg1212->dma_shared) < 0) {
+ snd_printk(KERN_ERR "korg1212: can not allocate shared buffer memory (%zd bytes)\n", sizeof(struct KorgSharedBuffer));
+ snd_korg1212_free(korg1212);
+ 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;
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ korg1212->DataBufsSize, &korg1212->dma_play) < 0) {
+ snd_printk(KERN_ERR "korg1212: can not allocate play data buffer memory (%d bytes)\n", korg1212->DataBufsSize);
+ snd_korg1212_free(korg1212);
+ 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);
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ korg1212->DataBufsSize, &korg1212->dma_rec) < 0) {
+ snd_printk(KERN_ERR "korg1212: can not allocate record data buffer memory (%d bytes)\n", korg1212->DataBufsSize);
+ snd_korg1212_free(korg1212);
+ 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) {
+ snd_printk(KERN_ERR "firmware not available\n");
+ snd_korg1212_free(korg1212);
+ return err;
+ }
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+ dsp_code->size, &korg1212->dma_dsp) < 0) {
+ snd_printk(KERN_ERR "korg1212: cannot allocate dsp code memory (%zd bytes)\n", dsp_code->size);
+ snd_korg1212_free(korg1212);
+ release_firmware(dsp_code);
+ 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);
+
+ release_firmware(dsp_code);
+
+ 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]);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, korg1212, &ops)) < 0) {
+ snd_korg1212_free(korg1212);
+ return err;
+ }
+
+ 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));
+
+ if ((err = snd_pcm_new(korg1212->card, "korg1212", 0, 1, 1, &korg1212->pcm)) < 0)
+ return err;
+
+ korg1212->pcm->private_data = korg1212;
+ korg1212->pcm->private_free = snd_korg1212_free_pcm;
+ strcpy(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);
+
+ * rchip = 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if ((err = snd_korg1212_create(card, pci, &korg1212)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ strcpy(card->driver, "korg1212");
+ strcpy(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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_korg1212_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver korg1212_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_korg1212_ids,
+ .probe = snd_korg1212_probe,
+ .remove = snd_korg1212_remove,
+};
+
+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..cdd8db79b
--- /dev/null
+++ b/sound/pci/lola/lola.c
@@ -0,0 +1,762 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for Digigram Lola PCI-e boards
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#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_SUPPORTED_DEVICE("{{Digigram, Lola}}");
+MODULE_DESCRIPTION("Digigram Lola driver");
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+
+#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)
+{
+ unsigned long flags;
+ 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);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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)
+{
+ int err;
+ unsigned char tmp;
+ unsigned long end_time;
+
+ err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ &chip->pci->dev,
+ PAGE_SIZE, &chip->rb);
+ if (err < 0)
+ return err;
+
+ 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 lola *chip)
+{
+ if (chip->initialized)
+ lola_stop_hw(chip);
+ lola_free_pcm(chip);
+ lola_free_mixer(chip);
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *)chip);
+ iounmap(chip->bar[0].remap_addr);
+ iounmap(chip->bar[1].remap_addr);
+ if (chip->rb.area)
+ snd_dma_free_pages(&chip->rb);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+}
+
+static int lola_dev_free(struct snd_device *device)
+{
+ lola_free(device->device_data);
+ return 0;
+}
+
+static int lola_create(struct snd_card *card, struct pci_dev *pci,
+ int dev, struct lola **rchip)
+{
+ struct lola *chip;
+ int err;
+ unsigned int dever;
+ static const struct snd_device_ops ops = {
+ .dev_free = lola_dev_free,
+ };
+
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&chip->reg_lock);
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ 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;
+ }
+
+ err = pci_request_regions(pci, DRVNAME);
+ if (err < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+
+ chip->bar[0].addr = pci_resource_start(pci, 0);
+ chip->bar[0].remap_addr = pci_ioremap_bar(pci, 0);
+ chip->bar[1].addr = pci_resource_start(pci, 2);
+ chip->bar[1].remap_addr = pci_ioremap_bar(pci, 2);
+ if (!chip->bar[0].remap_addr || !chip->bar[1].remap_addr) {
+ dev_err(chip->card->dev, "ioremap error\n");
+ err = -ENXIO;
+ goto errout;
+ }
+
+ pci_set_master(pci);
+
+ err = reset_controller(chip);
+ if (err < 0)
+ goto errout;
+
+ if (request_irq(pci->irq, lola_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq);
+ err = -EBUSY;
+ goto errout;
+ }
+ 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);
+ err = -EINVAL;
+ goto errout;
+ }
+
+ err = setup_corb_rirb(chip);
+ if (err < 0)
+ goto errout;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ dev_err(chip->card->dev, "Error creating device [card]!\n");
+ goto errout;
+ }
+
+ strcpy(card->driver, "Lola");
+ strlcpy(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);
+ strcpy(card->mixername, card->shortname);
+
+ lola_irq_enable(chip);
+
+ chip->initialized = 1;
+ *rchip = chip;
+ return 0;
+
+ errout:
+ lola_free(chip);
+ return err;
+}
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0) {
+ dev_err(&pci->dev, "Error creating card!\n");
+ return err;
+ }
+
+ err = lola_create(card, pci, dev, &chip);
+ if (err < 0)
+ goto out_free;
+ card->private_data = chip;
+
+ err = lola_parse_tree(chip);
+ if (err < 0)
+ goto out_free;
+
+ err = lola_create_pcm(chip);
+ if (err < 0)
+ goto out_free;
+
+ err = lola_create_mixer(chip);
+ if (err < 0)
+ goto out_free;
+
+ lola_proc_debug_new(chip);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto out_free;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return err;
+out_free:
+ snd_card_free(card);
+ return err;
+}
+
+static void lola_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+/* 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,
+ .remove = lola_remove,
+};
+
+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..8a598aa40
--- /dev/null
+++ b/sound/pci/lola/lola.h
@@ -0,0 +1,514 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Support for Digigram Lola PCI-e boards
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#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);
+void lola_free_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);
+void lola_save_mixer(struct lola *chip);
+void lola_restore_mixer(struct lola *chip);
+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..fdb85f256
--- /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 <tiwai@suse.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#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 */
+ }
+
+ /* ajustement */
+ 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 = (nitems + 3) / 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..e2c8f1417
--- /dev/null
+++ b/sound/pci/lola/lola_mixer.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for Digigram Lola PCI-e boards
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#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));
+
+ /* 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);
+}
+
+void lola_save_mixer(struct lola *chip)
+{
+ /* mute analog output */
+ if (chip->mixer.array_saved) {
+ /* store contents of mixer array */
+ memcpy_fromio(chip->mixer.array_saved, chip->mixer.array,
+ sizeof(*chip->mixer.array));
+ }
+ lola_setup_all_analog_gains(chip, PLAY, true); /* output mute */
+}
+
+void lola_restore_mixer(struct lola *chip)
+{
+ int i;
+
+ /*lola_reset_setups(chip);*/
+ if (chip->mixer.array_saved) {
+ /* restore contents of mixer array */
+ memcpy_toio(chip->mixer.array, chip->mixer.array_saved,
+ sizeof(*chip->mixer.array));
+ /* inform micro-controller about all restored values
+ * and ignore return values
+ */
+ for (i = 0; i < chip->mixer.src_phys_ins; i++)
+ lola_codec_write(chip, chip->mixer.nid,
+ LOLA_VERB_SET_SOURCE_GAIN,
+ i, 0);
+ for (i = 0; i < chip->mixer.src_stream_outs; i++)
+ lola_codec_write(chip, chip->mixer.nid,
+ LOLA_VERB_SET_SOURCE_GAIN,
+ chip->mixer.src_stream_out_ofs + i, 0);
+ for (i = 0; i < chip->mixer.dest_stream_ins; i++)
+ lola_codec_write(chip, chip->mixer.nid,
+ LOLA_VERB_SET_DESTINATION_GAIN,
+ i, 0);
+ 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);
+ 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..f647c7ed0
--- /dev/null
+++ b/sound/pci/lola/lola_pcm.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for Digigram Lola PCI-e boards
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#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;
+
+ mutex_lock(&chip->open_mutex);
+ if (str->opened) {
+ mutex_unlock(&chip->open_mutex);
+ 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);
+ mutex_unlock(&chip->open_mutex);
+ 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);
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->open_mutex);
+ 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);
+
+ mutex_lock(&chip->open_mutex);
+ lola_stream_reset(chip, str);
+ lola_cleanup_slave_streams(pcm, str);
+ mutex_unlock(&chip->open_mutex);
+ 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;
+
+ mutex_lock(&chip->open_mutex);
+ lola_stream_reset(chip, str);
+ lola_cleanup_slave_streams(pcm, str);
+ if (str->index + runtime->channels > pcm->num_streams) {
+ mutex_unlock(&chip->open_mutex);
+ return -EINVAL;
+ }
+ for (i = 1; i < runtime->channels; i++) {
+ str[i].master = str;
+ str[i].opened = 1;
+ }
+ mutex_unlock(&chip->open_mutex);
+
+ 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);
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ for (i = 0; bits && i < pcm->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++) {
+ err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ &chip->pci->dev,
+ PAGE_SIZE, &chip->pcm[i].bdl);
+ if (err < 0)
+ return err;
+ }
+
+ 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;
+ strlcpy(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;
+}
+
+void lola_free_pcm(struct lola *chip)
+{
+ snd_dma_free_pages(&chip->pcm[0].bdl);
+ snd_dma_free_pages(&chip->pcm[1].bdl);
+}
+
+/*
+ */
+
+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 <tiwai@suse.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#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..c295f68ba
--- /dev/null
+++ b/sound/pci/lx6464es/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-lx6464es-objs := 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..b92ea074f
--- /dev/null
+++ b/sound/pci/lx6464es/lx6464es.c
@@ -0,0 +1,1119 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* -*- linux-c -*- *
+ *
+ * ALSA driver for the digigram lx6464es interface
+ *
+ * Copyright (c) 2008, 2009 Tim Blechmann <tim@klingt.org>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+#include "lx6464es.h"
+
+MODULE_AUTHOR("Tim Blechmann");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("digigram lx6464es");
+MODULE_SUPPORTED_DEVICE("{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");
+ mutex_lock(&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");
+ goto exit;
+ }
+#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");
+ goto exit;
+ }
+
+ /* 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");
+ goto exit;
+ }
+
+ snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
+
+ snd_pcm_set_sync(substream);
+ err = 0;
+
+exit:
+ runtime->private_data = chip;
+
+ mutex_unlock(&chip->setup_mutex);
+ 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");
+
+ mutex_lock(&chip->lock);
+ pos = lx_stream->frame_pos * substream->runtime->period_size;
+ mutex_unlock(&chip->lock);
+
+ 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");
+
+ mutex_lock(&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);
+ goto exit;
+ }
+
+ err = lx_hardware_close(chip, substream);
+ if (err < 0) {
+ dev_err(chip->card->dev, "failed to close hardware. "
+ "Error code %d\n", err);
+ goto exit;
+ }
+ }
+
+ 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);
+ goto exit;
+ }
+
+ err = lx_hardware_start(chip, substream);
+ if (err < 0) {
+ dev_err(chip->card->dev, "failed to start hardware. "
+ "Error code %d\n", err);
+ goto exit;
+ }
+
+ chip->hardware_running[is_capture] = 1;
+
+ if (chip->board_sample_rate != substream->runtime->rate) {
+ if (!err)
+ chip->board_sample_rate = substream->runtime->rate;
+ }
+
+exit:
+ mutex_unlock(&chip->setup_mutex);
+ 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");
+
+ mutex_lock(&chip->setup_mutex);
+
+ if (is_capture)
+ chip->capture_stream.stream = substream;
+ else
+ chip->playback_stream.stream = substream;
+
+ mutex_unlock(&chip->setup_mutex);
+ 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");
+ mutex_lock(&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);
+ goto exit;
+ }
+
+ err = lx_hardware_close(chip, substream);
+ if (err < 0) {
+ dev_err(chip->card->dev, "failed to close hardware. "
+ "Error code %d\n", err);
+ goto exit;
+ }
+
+ chip->hardware_running[is_capture] = 0;
+ }
+
+ if (is_capture)
+ chip->capture_stream.stream = NULL;
+ else
+ chip->playback_stream.stream = NULL;
+
+exit:
+ mutex_unlock(&chip->setup_mutex);
+ return err;
+}
+
+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 channels = substream->runtime->channels;
+ const u32 bytes_per_frame = channels * 3;
+ const u32 period_size = substream->runtime->period_size;
+ const u32 periods = substream->runtime->periods;
+ const u32 period_bytes = period_size * bytes_per_frame;
+
+ 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)
+{
+ int err = 0;
+
+ mutex_lock(&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:
+ err = -EINVAL;
+ goto exit;
+ }
+
+ lx_trigger_dispatch_stream(chip, &chip->capture_stream);
+ lx_trigger_dispatch_stream(chip, &chip->playback_stream);
+
+exit:
+ mutex_unlock(&chip->lock);
+ return err;
+}
+
+
+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 int snd_lx6464es_free(struct lx6464es *chip)
+{
+ dev_dbg(chip->card->dev, "->snd_lx6464es_free\n");
+
+ lx_irq_disable(chip);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ iounmap(chip->port_dsp_bar);
+ ioport_unmap(chip->port_plx_remapped);
+
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+
+ kfree(chip);
+
+ return 0;
+}
+
+static int snd_lx6464es_dev_free(struct snd_device *device)
+{
+ return snd_lx6464es_free(device->device_data);
+}
+
+/* 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;
+ strcpy(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 **rchip)
+{
+ struct lx6464es *chip;
+ int err;
+
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_lx6464es_dev_free,
+ };
+
+ dev_dbg(card->dev, "->snd_lx6464es_create\n");
+
+ *rchip = NULL;
+
+ /* 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 */
+ 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");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ err = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ 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 = pci_request_regions(pci, card_name);
+ if (err < 0)
+ goto request_regions_failed;
+
+ /* plx port */
+ chip->port_plx = pci_resource_start(pci, 1);
+ chip->port_plx_remapped = ioport_map(chip->port_plx,
+ pci_resource_len(pci, 1));
+
+ /* dsp port */
+ chip->port_dsp_bar = pci_ioremap_bar(pci, 2);
+ if (!chip->port_dsp_bar) {
+ dev_err(card->dev, "cannot remap PCI memory region\n");
+ err = -ENOMEM;
+ goto remap_pci_failed;
+ }
+
+ err = request_threaded_irq(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);
+ goto request_irq_failed;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0)
+ goto device_new_failed;
+
+ 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;
+
+ *rchip = chip;
+ return 0;
+
+device_new_failed:
+ free_irq(pci->irq, chip);
+
+request_irq_failed:
+ iounmap(chip->port_dsp_bar);
+
+remap_pci_failed:
+ pci_release_regions(pci);
+
+request_regions_failed:
+ kfree(chip);
+
+alloc_failed:
+ pci_disable_device(pci);
+
+ return err;
+}
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ err = snd_lx6464es_create(card, pci, &chip);
+ if (err < 0) {
+ dev_err(card->dev, "error during snd_lx6464es_create\n");
+ goto out_free;
+ }
+
+ strcpy(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 out_free;
+
+ dev_dbg(chip->card->dev, "initialization successful\n");
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+out_free:
+ snd_card_free(card);
+ return err;
+
+}
+
+static void snd_lx6464es_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+
+static struct pci_driver lx6464es_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_lx6464es_ids,
+ .probe = snd_lx6464es_probe,
+ .remove = snd_lx6464es_remove,
+};
+
+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 <tim@klingt.org>
+ */
+
+#ifndef LX6464ES_H
+#define LX6464ES_H
+
+#include <linux/spinlock.h>
+#include <linux/atomic.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#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..a49a3254f
--- /dev/null
+++ b/sound/pci/lx6464es/lx_core.c
@@ -0,0 +1,1177 @@
+// 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 <tim@klingt.org>
+ */
+
+/* #define RMH_DEBUG 1 */
+
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+
+#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;
+
+ snd_printk(LXRMH "command %s\n", dsp_commands[idx].dcOpName);
+
+ for (i = 0; i != rmh->cmd_len; ++i)
+ snd_printk(LXRMH "\tcmd[%d] %08x\n", i, rmh->cmd[i]);
+
+ for (i = 0; i != rmh->stat_len; ++i)
+ snd_printk(LXRMH "\tstat[%d]: %08x\n", i, rmh->stat[i]);
+ snd_printk("\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)
+{
+ u16 ret;
+
+ mutex_lock(&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];
+ mutex_unlock(&chip->msg_lock);
+ return ret;
+}
+
+int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq)
+{
+ u16 ret = 0;
+ u32 freq_raw = 0;
+ u32 freq = 0;
+ u32 frequency = 0;
+
+ mutex_lock(&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;
+ }
+
+ mutex_unlock(&chip->msg_lock);
+
+ *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)
+{
+ int ret;
+
+ mutex_lock(&chip->msg_lock);
+
+ lx_message_init(&chip->rmh, CMD_02_SET_GRANULARITY);
+ chip->rmh.cmd[0] |= gran;
+
+ ret = lx_message_send_atomic(chip, &chip->rmh);
+ mutex_unlock(&chip->msg_lock);
+ return ret;
+}
+
+int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data)
+{
+ int ret;
+
+ mutex_lock(&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));
+
+ mutex_unlock(&chip->msg_lock);
+ 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);
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->msg_lock);
+
+ 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)
+{
+ int err;
+ u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+ mutex_lock(&chip->msg_lock);
+ lx_message_init(&chip->rmh, CMD_07_RELEASE_PIPE);
+
+ chip->rmh.cmd[0] |= pipe_cmd;
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+ mutex_unlock(&chip->msg_lock);
+
+ return err;
+}
+
+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;
+
+ mutex_lock(&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);
+ }
+ }
+
+ mutex_unlock(&chip->msg_lock);
+ return err;
+}
+
+
+int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+ int err;
+ u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+ mutex_lock(&chip->msg_lock);
+ lx_message_init(&chip->rmh, CMD_09_STOP_PIPE);
+
+ chip->rmh.cmd[0] |= pipe_cmd;
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+
+ mutex_unlock(&chip->msg_lock);
+ return err;
+}
+
+static int lx_pipe_toggle_state(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+ int err;
+ u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+ mutex_lock(&chip->msg_lock);
+ lx_message_init(&chip->rmh, CMD_0B_TOGGLE_PIPE_STATE);
+
+ chip->rmh.cmd[0] |= pipe_cmd;
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+
+ mutex_unlock(&chip->msg_lock);
+ return err;
+}
+
+
+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);
+
+ mutex_lock(&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 */
+ }
+
+ mutex_unlock(&chip->msg_lock);
+ 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);
+
+ mutex_lock(&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;
+
+ mutex_unlock(&chip->msg_lock);
+ 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, &current_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)
+{
+ int err;
+ u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+ mutex_lock(&chip->msg_lock);
+ lx_message_init(&chip->rmh, CMD_13_SET_STREAM_STATE);
+
+ chip->rmh.cmd[0] |= pipe_cmd;
+ chip->rmh.cmd[0] |= state;
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+ mutex_unlock(&chip->msg_lock);
+
+ return err;
+}
+
+int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime,
+ u32 pipe, int is_capture)
+{
+ int err;
+ u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+ u32 channels = runtime->channels;
+
+ if (runtime->channels != channels)
+ dev_err(chip->card->dev, "channel count mismatch: %d vs %d",
+ runtime->channels, channels);
+
+ mutex_lock(&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;
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+ mutex_unlock(&chip->msg_lock);
+
+ return err;
+}
+
+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);
+
+ mutex_lock(&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;
+
+ mutex_unlock(&chip->msg_lock);
+ 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);
+
+ mutex_lock(&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 */
+
+ mutex_unlock(&chip->msg_lock);
+ 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);
+
+ mutex_lock(&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];
+ goto done;
+ }
+
+ 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");
+
+ done:
+ mutex_unlock(&chip->msg_lock);
+ 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);
+
+ mutex_lock(&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;
+
+ mutex_unlock(&chip->msg_lock);
+ return err;
+}
+
+int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture,
+ u32 buffer_index)
+{
+ int err;
+ u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+ mutex_lock(&chip->msg_lock);
+ lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER);
+
+ chip->rmh.cmd[0] |= pipe_cmd;
+ chip->rmh.cmd[0] |= buffer_index;
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+
+ mutex_unlock(&chip->msg_lock);
+ return err;
+}
+
+
+/* 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)
+{
+ int err;
+ /* bit set to 1: channel muted */
+ u64 mute_mask = unmute ? 0 : 0xFFFFFFFFFFFFFFFFLLU;
+
+ mutex_lock(&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]);
+
+ err = lx_message_send_atomic(chip, &chip->rmh);
+
+ mutex_unlock(&chip->msg_lock);
+ return err;
+}
+
+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;
+
+ mutex_lock(&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;
+ }
+
+ mutex_unlock(&chip->msg_lock);
+ 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 channels = substream->runtime->channels;
+ const u32 bytes_per_frame = channels * 3;
+ const u32 period_size = substream->runtime->period_size;
+ const u32 period_bytes = period_size * bytes_per_frame;
+ 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");
+
+ mutex_lock(&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;
+ mutex_unlock(&chip->lock);
+
+ 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,
+ &notified_in_pipe_mask,
+ &notified_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..296013c91
--- /dev/null
+++ b/sound/pci/lx6464es/lx_core.h
@@ -0,0 +1,222 @@
+/* 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 <tim@klingt.org>
+ */
+
+#ifndef LX_CORE_H
+#define LX_CORE_H
+
+#include <linux/interrupt.h>
+
+#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)
+{
+ snd_printdd("->lx_stream_start\n");
+ 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)
+{
+ snd_printdd("->lx_stream_pause\n");
+ 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)
+{
+ snd_printdd("->lx_stream_stop\n");
+ 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 <tim@klingt.org>
+ */
+
+#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..40232a278
--- /dev/null
+++ b/sound/pci/maestro3.c
@@ -0,0 +1,2773 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for ESS Maestro3/Allegro (ES1988) soundcards.
+ * Copyright (c) 2000 by Zach Brown <zab@zabbo.net>
+ * Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 <rob@pangalactic.org>
+ */
+
+#define CARD_NAME "ESS Maestro3/Allegro/Canyon3D-2"
+#define DRIVER_NAME "Maestro3"
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/input.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <asm/byteorder.h>
+
+MODULE_AUTHOR("Zach Brown <zab@zabbo.net>, Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ESS Maestro3 PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,Maestro3 PCI},"
+ "{ESS,ES1988},"
+ "{ESS,Allegro PCI},"
+ "{ESS,Allegro-1 PCI},"
+ "{ESS,Canyon3D-2/LE PCI}}");
+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;
+
+#ifdef CONFIG_PM_SLEEP
+ u16 *suspend_mem;
+#endif
+
+ const struct firmware *assp_kernel_image;
+ const struct firmware *assp_minisrc_image;
+};
+
+/*
+ * pci ids
+ */
+static const struct pci_device_id snd_m3_ids[] = {
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ALLEGRO_1, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ALLEGRO, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_CANYON3D_2LE, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_CANYON3D_2, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_1, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_HW, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_2, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {0,},
+};
+
+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;
+
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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 = ((runtime->rate << 15) + 24000 ) / 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;
+
+ spin_lock_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);
+
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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;
+
+ spin_lock(&chip->reg_lock);
+ ptr = snd_m3_get_pointer(chip, s, subs);
+ spin_unlock(&chip->reg_lock);
+ 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 */
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+ }
+ }
+ }
+
+#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;
+
+ spin_lock_irq(&chip->reg_lock);
+ for (i = 0; i < chip->num_substreams; i++) {
+ s = &chip->substreams[i];
+ if (! s->opened)
+ goto __found;
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ return -ENOMEM;
+__found:
+ s->opened = 1;
+ s->running = 0;
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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 */
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+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;
+
+ if ((err = snd_m3_substream_open(chip, subs)) < 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;
+
+ if ((err = snd_m3_substream_open(chip, subs)) < 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;
+ strcpy(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;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+ struct snd_ctl_elem_id elem_id;
+#endif
+ int err;
+ static const struct snd_ac97_bus_ops ops = {
+ .write = snd_m3_ac97_write,
+ .read = snd_m3_ac97_read,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+ return err;
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = chip;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 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
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(elem_id.name, "Master Playback Switch");
+ chip->master_switch = snd_ctl_find_id(chip->card, &elem_id);
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(elem_id.name, "Master Playback Volume");
+ chip->master_volume = snd_ctl_find_id(chip->card, &elem_id);
+#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 int snd_m3_free(struct snd_m3 *chip)
+{
+ struct m3_dma *s;
+ int i;
+
+ cancel_work_sync(&chip->hwvol_work);
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+ if (chip->input_dev)
+ input_unregister_device(chip->input_dev);
+#endif
+
+ if (chip->substreams) {
+ spin_lock_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);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ kfree(chip->substreams);
+ }
+ if (chip->iobase) {
+ outw(0, chip->iobase + HOST_INT_CTRL); /* disable ints */
+ }
+
+#ifdef CONFIG_PM_SLEEP
+ vfree(chip->suspend_mem);
+#endif
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ if (chip->iobase)
+ pci_release_regions(chip->pci);
+
+ release_firmware(chip->assp_kernel_image);
+ release_firmware(chip->assp_minisrc_image);
+
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+
+/*
+ * APM support
+ */
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(m3_pm, m3_suspend, m3_resume);
+#define M3_PM_OPS &m3_pm
+#else
+#define M3_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+static int snd_m3_input_register(struct snd_m3 *chip)
+{
+ struct input_dev *input_dev;
+ int err;
+
+ input_dev = input_allocate_device();
+ 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) {
+ input_free_device(input_dev);
+ return err;
+ }
+
+ chip->input_dev = input_dev;
+ return 0;
+}
+#endif /* CONFIG_INPUT */
+
+/*
+ */
+
+static int snd_m3_dev_free(struct snd_device *device)
+{
+ struct snd_m3 *chip = device->device_data;
+ return snd_m3_free(chip);
+}
+
+static int
+snd_m3_create(struct snd_card *card, struct pci_dev *pci,
+ int enable_amp,
+ int amp_gpio,
+ struct snd_m3 **chip_ret)
+{
+ struct snd_m3 *chip;
+ int i, err;
+ const struct snd_pci_quirk *quirk;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_m3_dev_free,
+ };
+
+ *chip_ret = NULL;
+
+ if (pci_enable_device(pci))
+ return -EIO;
+
+ /* check, if we can restrict PCI DMA transfers to 28 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(28)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(28)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 28bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ 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);
+
+ 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 = kcalloc(chip->num_substreams, sizeof(struct m3_dma),
+ GFP_KERNEL);
+ if (chip->substreams == NULL) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ err = request_firmware(&chip->assp_kernel_image,
+ "ess/maestro3_assp_kernel.fw", &pci->dev);
+ if (err < 0)
+ goto free_chip;
+
+ err = request_firmware(&chip->assp_minisrc_image,
+ "ess/maestro3_assp_minisrc.fw", &pci->dev);
+ if (err < 0)
+ goto free_chip;
+
+ err = pci_request_regions(pci, card->driver);
+ if (err < 0)
+ goto free_chip;
+
+ 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 (request_irq(pci->irq, snd_m3_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ err = -ENOMEM;
+ goto free_chip;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+#ifdef CONFIG_PM_SLEEP
+ chip->suspend_mem =
+ vmalloc(array_size(sizeof(u16),
+ REV_B_CODE_MEMORY_LENGTH +
+ REV_B_DATA_MEMORY_LENGTH));
+ if (chip->suspend_mem == NULL)
+ dev_warn(card->dev, "can't allocate apm buffer\n");
+#endif
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0)
+ goto free_chip;
+
+ if ((err = snd_m3_mixer(chip)) < 0)
+ return err;
+
+ for (i = 0; i < chip->num_substreams; i++) {
+ struct m3_dma *s = &chip->substreams[i];
+ if ((err = snd_m3_assp_client_init(chip, s, i)) < 0)
+ return err;
+ }
+
+ if ((err = snd_m3_pcm(chip, 0)) < 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);
+
+ *chip_ret = chip;
+
+ return 0;
+
+free_chip:
+ snd_m3_free(chip);
+ return err;
+}
+
+/*
+ */
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ switch (pci->device) {
+ case PCI_DEVICE_ID_ESS_ALLEGRO:
+ case PCI_DEVICE_ID_ESS_ALLEGRO_1:
+ strcpy(card->driver, "Allegro");
+ break;
+ case PCI_DEVICE_ID_ESS_CANYON3D_2LE:
+ case PCI_DEVICE_ID_ESS_CANYON3D_2:
+ strcpy(card->driver, "Canyon3D-2");
+ break;
+ default:
+ strcpy(card->driver, "Maestro3");
+ break;
+ }
+
+ err = snd_m3_create(card, pci, external_amp[dev], amp_gpio[dev], &chip);
+ if (err < 0)
+ goto free_card;
+
+ card->private_data = chip;
+
+ 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)
+ goto free_card;
+
+#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;
+
+free_card:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_m3_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver m3_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_m3_ids,
+ .probe = snd_m3_probe,
+ .remove = snd_m3_remove,
+ .driver = {
+ .pm = M3_PM_OPS,
+ },
+};
+
+module_pci_driver(m3_driver);
diff --git a/sound/pci/mixart/Makefile b/sound/pci/mixart/Makefile
new file mode 100644
index 000000000..16cfeb78a
--- /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 <perex@perex.cz>
+#
+
+snd-mixart-objs := 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..efff220b2
--- /dev/null
+++ b/sound/pci/mixart/mixart.c
@@ -0,0 +1,1402 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * main file with alsa callbacks
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ */
+
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "mixart.h"
+#include "mixart_hwdep.h"
+#include "mixart_core.h"
+#include "mixart_mixer.h"
+
+#define CARD_NAME "miXart"
+
+MODULE_AUTHOR("Digigram <alsa@digigram.com>");
+MODULE_DESCRIPTION("Digigram " CARD_NAME);
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram," CARD_NAME "}}");
+
+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), 0, }, /* MC8240 */
+ { 0, }
+};
+
+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[0] = 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[0] = 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(sizeof(*buf), GFP_KERNEL);
+ 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; i<stream_count; i++) {
+ int j;
+ struct mixart_flowinfo *flowinfo;
+ struct mixart_bufferinfo *bufferinfo;
+
+ /* we don't yet know the format, so config 16 bit pcm audio for instance */
+ buf->sgroup_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[0].uid_pipe = stream->pipe->group_uid;
+ stream_param.stream_desc[0].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);
+
+ mutex_lock(&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) {
+ mutex_unlock(&mgr->setup_mutex);
+ 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);
+ }
+ mutex_unlock(&mgr->setup_mutex);
+
+ 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;
+
+ mutex_lock(&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);
+ err = -EBUSY;
+ goto _exit_open;
+ }
+
+ /* get pipe pointer (out pipe) */
+ pipe = snd_mixart_add_ref_pipe(chip, pcm_number, 0, 0);
+
+ if (pipe == NULL) {
+ err = -EINVAL;
+ goto _exit_open;
+ }
+
+ /* 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);
+ err = -EINVAL;
+ goto _exit_open;
+ }
+
+ 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;
+ }
+ }
+
+ _exit_open:
+ mutex_unlock(&mgr->setup_mutex);
+
+ return err;
+}
+
+
+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;
+
+ mutex_lock(&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);
+ err = -EBUSY;
+ goto _exit_open;
+ }
+
+ /* get pipe pointer (in pipe) */
+ pipe = snd_mixart_add_ref_pipe(chip, pcm_number, 1, 0);
+
+ if (pipe == NULL) {
+ err = -EINVAL;
+ goto _exit_open;
+ }
+
+ /* 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);
+ err = -EINVAL;
+ goto _exit_open;
+ }
+
+ 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;
+ }
+ }
+
+ _exit_open:
+ mutex_unlock(&mgr->setup_mutex);
+
+ return err;
+}
+
+
+
+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;
+
+ mutex_lock(&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;
+
+ mutex_unlock(&mgr->setup_mutex);
+ 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);
+ if ((err = snd_pcm_new(chip->card, name, MIXART_PCM_ANALOG,
+ MIXART_PLAYBACK_STREAMS,
+ MIXART_CAPTURE_STREAMS, &pcm)) < 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;
+ strcpy(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);
+ if ((err = snd_pcm_new(chip->card, name, MIXART_PCM_DIGITAL,
+ MIXART_PLAYBACK_STREAMS,
+ MIXART_CAPTURE_STREAMS, &pcm)) < 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;
+ strcpy(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(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->card = card;
+ chip->chip_idx = idx;
+ chip->mgr = mgr;
+ card->sync_irq = mgr->irq;
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 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 */
+ if ((err = pci_enable_device(pci)) < 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(sizeof(*mgr), GFP_KERNEL);
+ if (! mgr) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ mgr->pci = pci;
+ mgr->irq = -1;
+
+ /* resource assignment */
+ if ((err = pci_request_regions(pci, CARD_NAME)) < 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;
+ }
+
+ strcpy(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);
+
+ if ((err = snd_mixart_create(mgr, card, i)) < 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]);
+ }
+
+ if ((err = snd_card_register(card)) < 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..cbed6d9a9
--- /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 <alsa@digigram.com>
+ */
+
+#ifndef __SOUND_MIXART_H
+#define __SOUND_MIXART_H
+
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <sound/pcm.h>
+
+#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_period_pos 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..fb8895af0
--- /dev/null
+++ b/sound/pci/mixart/mixart_core.c
@@ -0,0 +1,588 @@
+// 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 <alsa@digigram.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#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_DEFAULT_SIZE 512
+
+#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);
+
+ mutex_lock(&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) {
+ mutex_unlock(&mgr->msg_lock);
+ return err;
+ }
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&mgr->msg_sleep, &wait);
+ mutex_unlock(&mgr->msg_lock);
+ 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;
+
+ mutex_lock(&mgr->msg_lock);
+ err = get_msg(mgr, &resp, msg_frame);
+ mutex_unlock(&mgr->msg_lock);
+
+ 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);
+
+ mutex_lock(&mgr->msg_lock);
+ /* send the message */
+ err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 1, &notif_event); /* send and mark the notification event pending */
+ if(err) {
+ mutex_unlock(&mgr->msg_lock);
+ return err;
+ }
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&mgr->msg_sleep, &wait);
+ mutex_unlock(&mgr->msg_lock);
+ 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) */
+ mutex_lock(&mgr->msg_lock);
+ err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 0, &message_frame);
+ mutex_unlock(&mgr->msg_lock);
+
+ /* 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;
+
+ mutex_lock(&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;
+
+ for(i=0; i<notify->stream_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*/
+ mutex_lock(&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);
+ }
+ mutex_unlock(&mgr->msg_lock);
+ 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));
+
+ mutex_unlock(&mgr->lock);
+
+ 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..fbf4731a2
--- /dev/null
+++ b/sound/pci/mixart/mixart_core.h
@@ -0,0 +1,558 @@
+/* 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 <alsa@digigram.com>
+ */
+
+#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,
+};
+
+
+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];
+} __attribute__((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 */
+} __attribute__((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 */
+} __attribute__((packed));
+
+struct mixart_digital_hw_info
+{
+ u32 hw_connection_type;
+ u32 presence;
+ u32 clock;
+ u32 reserved;
+} __attribute__((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;
+} __attribute__((packed));
+
+struct mixart_digital_info
+{
+ u32 type_mask;
+ struct mixart_digital_hw_info aes_info;
+ struct mixart_digital_hw_info adat_info;
+} __attribute__((packed));
+
+struct mixart_audio_info
+{
+ u32 clock_type_mask;
+ struct mixart_analog_info analog_info;
+ struct mixart_digital_info digital_info;
+} __attribute__((packed));
+
+struct mixart_audio_info_resp
+{
+ u32 txx_status;
+ struct mixart_audio_info info;
+} __attribute__((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 */
+} __attribute__((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];
+} __attribute__((packed));
+
+struct mixart_stream_desc
+{
+ struct mixart_uid stream_uid;
+ u32 stream_desc;
+} __attribute__((packed));
+
+struct mixart_streaming_group
+{
+ u32 status;
+ struct mixart_uid group;
+ u32 pipe_desc;
+ u32 stream_count;
+ struct mixart_stream_desc stream[32];
+} __attribute__((packed));
+
+/* MSG_STREAM_DELETE_GROUP */
+
+/* request : mixart_uid_t group */
+
+struct mixart_delete_group_resp
+{
+ u32 status;
+ u32 unused[2];
+} __attribute__((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;
+} __attribute__((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];
+} __attribute__((packed));
+
+struct mixart_flow_info
+{
+ struct mixart_txx_stream_desc stream_desc;
+ u32 flow_entry;
+ u32 flow_phy_addr;
+} __attribute__((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] */
+} __attribute__((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[1]; /* could be an array[pipe_count] */
+} __attribute__((packed));
+
+struct mixart_group_state_resp
+{
+ u32 txx_status;
+ u64 scheduler;
+} __attribute__((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;
+} __attribute__((packed));
+
+struct mixart_timer_notify
+{
+ u32 stream_count;
+ struct mixart_sample_pos streams[MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS];
+} __attribute__((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;
+} __attribute__((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[1];
+} __attribute__((packed));
+
+struct mixart_clock_properties_resp
+{
+ u32 status;
+ u32 clock_mode;
+} __attribute__((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[1]; /* only one stream per command, but this could be an array */
+
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((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];
+} __attribute__((packed));
+
+
+/* MSG_PHYSICALIO_SET_LEVEL = 0x0F0008,
+ MSG_PHYSICALIO_GET_LEVEL = 0x0F000C,
+*/
+
+struct mixart_io_channel_level
+{
+ u32 analog_level; /* float */
+ u32 unused[2];
+} __attribute__((packed));
+
+struct mixart_io_level
+{
+ s32 channel; /* 0=left, 1=right, -1=both, -2=both same */
+ struct mixart_io_channel_level level[2];
+} __attribute__((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;
+} __attribute__((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];
+} __attribute__((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;
+} __attribute__((packed));
+
+struct mixart_set_out_stream_level
+{
+ struct mixart_txx_stream_desc desc;
+ struct mixart_out_stream_level_info out_level;
+} __attribute__((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 */
+} __attribute__((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..13dcb2fd0
--- /dev/null
+++ b/sound/pci/mixart/mixart_hwdep.c
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * DSP firmware management
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include "mixart.h"
+#include "mixart_mixer.h"
+#include "mixart_core.h"
+#include "mixart_hwdep.h"
+
+
+/**
+ * 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(sizeof(*connector), GFP_KERNEL);
+ audio_info_req = kmalloc(sizeof(*audio_info_req), GFP_KERNEL);
+ audio_info = kmalloc(sizeof(*audio_info), GFP_KERNEL);
+ 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; k<mgr->num_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;
+
+ if((err = mixart_enum_connectors(mgr)) < 0) return err;
+
+ if((err = mixart_enum_physio(mgr)) < 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];
+
+ if ((err = snd_mixart_create_pcm(chip)) < 0)
+ return err;
+
+ if (card_index == 0) {
+ if ((err = snd_mixart_create_mixer(chip->mgr)) < 0)
+ return err;
+ }
+
+ if ((err = snd_card_register(chip->card)) < 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];
+
+ const struct firmware *fw_entry;
+ int i, err;
+
+ for (i = 0; i < 3; i++) {
+ sprintf(path, "mixart/%s", fw_files[i]);
+ 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);
+ release_firmware(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 <alsa@digigram.com>
+ */
+
+#ifndef __SOUND_MIXART_HWDEP_H
+#define __SOUND_MIXART_HWDEP_H
+
+#include <sound/hwdep.h>
+
+#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..d2e7c3381
--- /dev/null
+++ b/sound/pci/mixart/mixart_mixer.c
@@ -0,0 +1,1181 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * mixer callbacks
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ */
+
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include "mixart.h"
+#include "mixart_core.h"
+#include "mixart_hwdep.h"
+#include <sound/control.h>
+#include <sound/tlv.h>
+#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);
+ mutex_lock(&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];
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->analog_playback_active[0];
+ ucontrol->value.integer.value[1] = chip->analog_playback_active[1];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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);
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&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];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&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);
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->monitoring_volume[0];
+ ucontrol->value.integer.value[1] = chip->monitoring_volume[1];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->monitoring_active[0];
+ ucontrol->value.integer.value[1] = chip->monitoring_active[1];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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<<i); /* mask 0x01 ans 0x02 */
+ }
+ }
+ if (changed) {
+ /* allocate or release resources for monitoring */
+ int allocate = chip->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);
+ }
+ }
+
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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; i<mgr->num_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 */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+ return err;
+ /* output mute controls */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_output_switch, chip))) < 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 */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+ return err;
+ }
+
+ temp = snd_mixart_pcm_vol;
+ temp.name = "PCM Playback Volume";
+ temp.count = MIXART_PLAYBACK_STREAMS;
+ temp.private_value = 0; /* playback analog */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+ return err;
+
+ temp.name = "PCM Capture Volume";
+ temp.count = 1;
+ temp.private_value = MIXART_VOL_REC_MASK; /* capture analog */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 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 */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 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 */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+ return err;
+ }
+ temp = mixart_control_pcm_switch;
+ temp.name = "PCM Playback Switch";
+ temp.private_value = 0; /* playback analog */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 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 */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+ return err;
+ }
+
+ /* monitoring */
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_monitor_vol, chip))) < 0)
+ return err;
+ if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_monitor_sw, chip))) < 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 <alsa@digigram.com>
+ */
+
+#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..3063766ac
--- /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 <perex@perex.cz>
+#
+
+snd-nm256-objs := 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..975994623
--- /dev/null
+++ b/sound/pci/nm256/nm256.c
@@ -0,0 +1,1757 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for NeoMagic 256AV and 256ZX chipsets.
+ * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#define CARD_NAME "NeoMagic 256AV/ZX"
+#define DRIVER_NAME "NM256"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("NeoMagic NM256AV/ZX");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{NeoMagic,NM256AV},"
+ "{NeoMagic,NM256ZX}}");
+
+/*
+ * 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 */
+ struct resource *res_cport; /* its resource */
+ unsigned long cport_addr; /* physical address */
+
+ void __iomem *buffer; /* buffer */
+ struct resource *res_buffer; /* its resource */
+ 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), 0},
+ {PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO), 0},
+ {PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO), 0},
+ {0,},
+};
+
+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)
+{
+ mutex_lock(&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);
+ mutex_unlock(&chip->irq_mutex);
+ return -EBUSY;
+ }
+ chip->irq = chip->pci->irq;
+ chip->card->sync_irq = chip->irq;
+ }
+ chip->irq_acks++;
+ mutex_unlock(&chip->irq_mutex);
+ return 0;
+}
+
+/* release interrupt */
+static void snd_nm256_release_irq(struct nm256 *chip)
+{
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->irq_mutex);
+}
+
+/*
+ * 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;
+ int err = 0;
+
+ if (snd_BUG_ON(!s))
+ return -ENXIO;
+
+ spin_lock(&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:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock(&chip->reg_lock);
+ return err;
+}
+
+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;
+ int err = 0;
+
+ if (snd_BUG_ON(!s))
+ return -ENXIO;
+
+ spin_lock(&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:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock(&chip->reg_lock);
+ return err;
+}
+
+
+/*
+ * 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;
+
+ spin_lock_irq(&chip->reg_lock);
+ s->running = 0;
+ snd_nm256_set_format(chip, s, substream);
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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,
+ void __user *src, unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nm256_stream *s = runtime->private_data;
+
+ if (copy_from_user_toio(s->bufptr + pos, src, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int
+snd_nm256_playback_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *src, unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nm256_stream *s = runtime->private_data;
+
+ memcpy_toio(s->bufptr + pos, src, count);
+ return 0;
+}
+
+/*
+ * copy to user
+ */
+static int
+snd_nm256_capture_copy(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void __user *dst, unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nm256_stream *s = runtime->private_data;
+
+ if (copy_to_user_fromio(dst, s->bufptr + pos, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int
+snd_nm256_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *dst, unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nm256_stream *s = runtime->private_data;
+
+ memcpy_fromio(dst, s->bufptr + pos, count);
+ 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_user = snd_nm256_playback_copy,
+ .copy_kernel = snd_nm256_playback_copy_kernel,
+ .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_user = snd_nm256_capture_copy,
+ .copy_kernel = snd_nm256_capture_copy_kernel,
+#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. */
+
+ spin_lock(&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);
+ }
+
+ spin_unlock(&chip->reg_lock);
+ 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. */
+
+ spin_lock(&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);
+ }
+
+ spin_unlock(&chip->reg_lock);
+ 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 = kcalloc(ARRAY_SIZE(nm256_ac97_init_val),
+ sizeof(short), GFP_KERNEL);
+ if (! chip->ac97_regs)
+ return -ENOMEM;
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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) {
+ spin_lock_irq(&chip->reg_lock);
+ snd_nm256_set_format(chip, s, s->substream);
+ spin_unlock_irq(&chip->reg_lock);
+ }
+ }
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ chip->in_resume = 0;
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(nm256_pm, nm256_suspend, nm256_resume);
+#define NM256_PM_OPS &nm256_pm
+#else
+#define NM256_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static int snd_nm256_free(struct nm256 *chip)
+{
+ 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);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ iounmap(chip->cport);
+ iounmap(chip->buffer);
+ release_and_free_resource(chip->res_cport);
+ release_and_free_resource(chip->res_buffer);
+
+ pci_disable_device(chip->pci);
+ kfree(chip->ac97_regs);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_nm256_dev_free(struct snd_device *device)
+{
+ struct nm256 *chip = device->device_data;
+ return snd_nm256_free(chip);
+}
+
+static int
+snd_nm256_create(struct snd_card *card, struct pci_dev *pci,
+ struct nm256 **chip_ret)
+{
+ struct nm256 *chip;
+ int err, pval;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_nm256_dev_free,
+ };
+ u32 addr;
+
+ *chip_ret = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ 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);
+
+ /* Init the memory port info. */
+ /* remap control port (#2) */
+ chip->res_cport = request_mem_region(chip->cport_addr, NM_PORT2_SIZE,
+ card->driver);
+ if (chip->res_cport == NULL) {
+ dev_err(card->dev, "memory region 0x%lx (size 0x%x) busy\n",
+ chip->cport_addr, NM_PORT2_SIZE);
+ err = -EBUSY;
+ goto __error;
+ }
+ chip->cport = ioremap(chip->cport_addr, NM_PORT2_SIZE);
+ if (chip->cport == NULL) {
+ dev_err(card->dev, "unable to map control port %lx\n",
+ chip->cport_addr);
+ err = -ENOMEM;
+ goto __error;
+ }
+
+ 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");
+ err = -ENXIO;
+ goto __error;
+ }
+ }
+ 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 */
+ if ((err = snd_nm256_peek_for_sig(chip)) < 0)
+ goto __error;
+ }
+
+ 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->res_buffer = request_mem_region(chip->buffer_addr,
+ chip->buffer_size,
+ card->driver);
+ if (chip->res_buffer == NULL) {
+ dev_err(card->dev, "buffer 0x%lx (size 0x%x) busy\n",
+ chip->buffer_addr, chip->buffer_size);
+ err = -EBUSY;
+ goto __error;
+ }
+ chip->buffer = ioremap(chip->buffer_addr, chip->buffer_size);
+ if (chip->buffer == NULL) {
+ err = -ENOMEM;
+ dev_err(card->dev, "unable to map ring buffer at %lx\n",
+ chip->buffer_addr);
+ goto __error;
+ }
+
+ /* 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? */
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0)
+ goto __error;
+
+ *chip_ret = chip;
+ return 0;
+
+__error:
+ snd_nm256_free(chip);
+ return err;
+}
+
+
+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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ switch (pci->device) {
+ case PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO:
+ strcpy(card->driver, "NM256AV");
+ break;
+ case PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO:
+ strcpy(card->driver, "NM256ZX");
+ break;
+ case PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO:
+ strcpy(card->driver, "NM256XL+");
+ break;
+ default:
+ dev_err(&pci->dev, "invalid device id 0x%x\n", pci->device);
+ snd_card_free(card);
+ 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;
+ if ((err = snd_nm256_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = chip;
+
+ 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;
+ }
+
+ if ((err = snd_nm256_pcm(chip, 0)) < 0 ||
+ (err = snd_nm256_mixer(chip)) < 0) {
+ snd_card_free(card);
+ 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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ return 0;
+}
+
+static void snd_nm256_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+
+static struct pci_driver nm256_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_nm256_ids,
+ .probe = snd_nm256_probe,
+ .remove = snd_nm256_remove,
+ .driver = {
+ .pm = NM256_PM_OPS,
+ },
+};
+
+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..0dfc4f840
--- /dev/null
+++ b/sound/pci/oxygen/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
+snd-oxygen-objs := oxygen.o xonar_dg_mixer.o xonar_dg.o
+snd-se6x-objs := se6x.o
+snd-virtuoso-objs := 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..a751fcce7
--- /dev/null
+++ b/sound/pci/oxygen/oxygen.c
@@ -0,0 +1,868 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * C-Media CMI8788 driver for C-Media's reference design and similar models
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+/*
+ * 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
+ *
+ * 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 <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "xonar_dg.h"
+#include "ak4396.h"
+#include "wm8785.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8786}"
+ ",{C-Media,CMI8787}"
+ ",{C-Media,CMI8788}}");
+
+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_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 },
+ { }
+};
+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
+
+struct generic_data {
+ unsigned int dacs;
+ u8 ak4396_regs[4][5];
+ u16 wm8785_regs[3];
+};
+
+static void ak4396_write(struct oxygen *chip, unsigned int codec,
+ u8 reg, u8 value)
+{
+ /* maps ALSA channel pair number to SPI output */
+ static const u8 codec_spi_map[4] = {
+ 0, 1, 2, 4
+ };
+ struct generic_data *data = chip->model_data;
+
+ oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+ OXYGEN_SPI_DATA_LENGTH_2 |
+ OXYGEN_SPI_CLOCK_160 |
+ (codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
+ OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+ AK4396_WRITE | (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 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;
+
+ data->dacs = chip->model.dac_channels_pcm / 2;
+ 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);
+}
+
+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 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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);
+
+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_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_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,
+ .remove = oxygen_pci_remove,
+#ifdef CONFIG_PM_SLEEP
+ .driver = {
+ .pm = &oxygen_pci_pm,
+ },
+#endif
+};
+
+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..06bf7e574
--- /dev/null
+++ b/sound/pci/oxygen/oxygen.h
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef OXYGEN_H_INCLUDED
+#define OXYGEN_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#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
+ )
+ );
+void oxygen_pci_remove(struct pci_dev *pci);
+#ifdef CONFIG_PM_SLEEP
+extern const struct dev_pm_ops oxygen_pci_pm;
+#endif
+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 <clemens@ladisch.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#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..afc6dd329
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_lib.c
@@ -0,0 +1,816 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * C-Media CMI8788 driver - main driver module
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/mpu401.h>
+#include <sound/pcm.h>
+#include "oxygen.h"
+#include "cm9780.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+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;
+
+ spin_lock(&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;
+
+ spin_unlock(&chip->reg_lock);
+
+ 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) {
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+ }
+
+ 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);
+ 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) {
+ /*
+ * 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);
+ }
+ }
+ }
+ spin_unlock_irq(&chip->reg_lock);
+
+ if (chip->controls[CONTROL_SPDIF_INPUT_BITS]) {
+ spin_lock_irq(&chip->reg_lock);
+ chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT;
+ oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+ chip->interrupt_mask);
+ spin_unlock_irq(&chip->reg_lock);
+
+ /*
+ * 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)
+{
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+static void oxygen_card_free(struct snd_card *card)
+{
+ struct oxygen *chip = card->private_data;
+
+ oxygen_shutdown(chip);
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ flush_work(&chip->spdif_input_bits_work);
+ flush_work(&chip->gpio_work);
+ chip->model.cleanup(chip);
+ kfree(chip->model_data);
+ mutex_destroy(&chip->mutex);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+}
+
+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_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 = pci_enable_device(pci);
+ if (err < 0)
+ goto err_card;
+
+ err = pci_request_regions(pci, DRIVER);
+ if (err < 0) {
+ dev_err(card->dev, "cannot reserve PCI resources\n");
+ goto err_pci_enable;
+ }
+
+ 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");
+ err = -ENXIO;
+ goto err_pci_regions;
+ }
+ chip->addr = pci_resource_start(pci, 0);
+
+ pci_id = oxygen_search_pci_id(chip, ids);
+ if (!pci_id) {
+ err = -ENODEV;
+ goto err_pci_regions;
+ }
+ oxygen_restore_eeprom(chip, pci_id);
+ err = get_model(chip, pci_id);
+ if (err < 0)
+ goto err_pci_regions;
+
+ if (chip->model.model_data_size) {
+ chip->model_data = kzalloc(chip->model.model_data_size,
+ GFP_KERNEL);
+ if (!chip->model_data) {
+ err = -ENOMEM;
+ goto err_pci_regions;
+ }
+ }
+
+ pci_set_master(pci);
+ card->private_free = oxygen_card_free;
+
+ configure_pcie_bridge(pci);
+ oxygen_init(chip);
+ chip->model.init(chip);
+
+ err = request_irq(pci->irq, oxygen_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip);
+ if (err < 0) {
+ dev_err(card->dev, "cannot grab interrupt %d\n", pci->irq);
+ goto err_card;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ strcpy(card->driver, chip->model.chip);
+ strcpy(card->shortname, chip->model.shortname);
+ sprintf(card->longname, "%s at %#lx, irq %i",
+ chip->model.longname, chip->addr, chip->irq);
+ strcpy(card->mixername, chip->model.chip);
+ snd_component_add(card, chip->model.chip);
+
+ err = oxygen_pcm_init(chip);
+ if (err < 0)
+ goto err_card;
+
+ err = oxygen_mixer_init(chip);
+ if (err < 0)
+ goto err_card;
+
+ 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)
+ goto err_card;
+ }
+
+ oxygen_proc_init(chip);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto err_card;
+
+ pci_set_drvdata(pci, card);
+ return 0;
+
+err_pci_regions:
+ pci_release_regions(pci);
+err_pci_enable:
+ pci_disable_device(pci);
+err_card:
+ snd_card_free(card);
+ return err;
+}
+EXPORT_SYMBOL(oxygen_pci_probe);
+
+void oxygen_pci_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+EXPORT_SYMBOL(oxygen_pci_remove);
+
+#ifdef CONFIG_PM_SLEEP
+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);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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;
+}
+
+SIMPLE_DEV_PM_OPS(oxygen_pci_pm, oxygen_pci_suspend, oxygen_pci_resume);
+EXPORT_SYMBOL(oxygen_pci_pm);
+#endif /* CONFIG_PM_SLEEP */
+
+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..eb3aca163
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_mixer.c
@@ -0,0 +1,1127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * C-Media CMI8788 driver - mixer code
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+#include <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#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;
+
+ mutex_lock(&chip->mutex);
+ for (i = 0; i < chip->model.dac_channels_mixer; ++i)
+ value->value.integer.value[i] = chip->dac_volume[i];
+ mutex_unlock(&chip->mutex);
+ 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;
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+static int dac_mute_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *value)
+{
+ struct oxygen *chip = ctl->private_data;
+
+ mutex_lock(&chip->mutex);
+ value->value.integer.value[0] = !chip->dac_mute;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ value->value.enumerated.item[0] = chip->dac_routing;
+ mutex_unlock(&chip->mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+static int spdif_switch_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *value)
+{
+ struct oxygen *chip = ctl->private_data;
+
+ mutex_lock(&chip->mutex);
+ value->value.integer.value[0] = chip->spdif_playback_enable;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ oxygen_to_iec958(chip->spdif_bits, value);
+ mutex_unlock(&chip->mutex);
+ 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);
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ oxygen_to_iec958(chip->spdif_pcm_bits, value);
+ mutex_unlock(&chip->mutex);
+ 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);
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ reg = oxygen_read_ac97(chip, codec, index);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ reg = oxygen_read_ac97(chip, codec, index);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ value->value.enumerated.item[0] =
+ !!(oxygen_read_ac97(chip, 0, CM9780_JACK) & CM9780_FMIC2MIC);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ reg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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..b2a3fcfe3
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_pcm.c
@@ -0,0 +1,783 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * C-Media CMI8788 driver - PCM code
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+#include <linux/pci.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+
+ 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);
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ mutex_lock(&chip->mutex);
+ chip->model.set_adc_params(chip, hw_params);
+ mutex_unlock(&chip->mutex);
+ 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);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ if (!is_ac97) {
+ mutex_lock(&chip->mutex);
+ chip->model.set_adc_params(chip, hw_params);
+ mutex_unlock(&chip->mutex);
+ }
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ if (!is_spdif) {
+ mutex_lock(&chip->mutex);
+ chip->model.set_adc_params(chip, hw_params);
+ mutex_unlock(&chip->mutex);
+ }
+ 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;
+
+ mutex_lock(&chip->mutex);
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ chip->model.set_dac_params(chip, hw_params);
+ oxygen_update_dac_routing(chip);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+
+ return 0;
+}
+
+static int oxygen_spdif_hw_free(struct snd_pcm_substream *substream)
+{
+ struct oxygen *chip = snd_pcm_substream_chip(substream);
+
+ spin_lock_irq(&chip->reg_lock);
+ oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+ OXYGEN_SPDIF_OUT_ENABLE);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+ }
+ }
+
+ spin_lock(&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);
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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..78c35a0a5
--- /dev/null
+++ b/sound/pci/oxygen/se6x.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * C-Media CMI8787 driver for the Studio Evolution SE6X
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+/*
+ * 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 <linux/pci.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include "oxygen.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Studio Evolution SE6X driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Studio Evolution,SE6X}}");
+
+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,
+ .remove = oxygen_pci_remove,
+#ifdef CONFIG_PM_SLEEP
+ .driver = {
+ .pm = &oxygen_pci_pm,
+ },
+#endif
+ .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..98ab16329
--- /dev/null
+++ b/sound/pci/oxygen/virtuoso.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * C-Media CMI8788 driver for Asus Xonar cards
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include "xonar.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Asus Virtuoso driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Asus,AV66},{Asus,AV100},{Asus,AV200}}");
+
+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,
+ .remove = oxygen_pci_remove,
+#ifdef CONFIG_PM_SLEEP
+ .driver = {
+ .pm = &oxygen_pci_pm,
+ },
+#endif
+ .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 <broonie@opensource.wolfsonmicro.com>
+ */
+
+#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..664b7759d
--- /dev/null
+++ b/sound/pci/oxygen/xonar_cs43xx.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * card driver for models with CS4398/CS4362A DACs (Xonar D1/DX)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+/*
+ * 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 <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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) {
+ spin_lock_irq(&chip->reg_lock);
+ oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+ mute ? GPIO_D1_INPUT_ROUTE : 0,
+ GPIO_D1_INPUT_ROUTE);
+ spin_unlock_irq(&chip->reg_lock);
+ }
+}
+
+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 <clemens@ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron@mail.ru>
+ */
+
+/*
+ * 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 <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#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..198588562
--- /dev/null
+++ b/sound/pci/oxygen/xonar_dg_mixer.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Mixer controls for the Xonar DG/DGX
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron@mail.ru>
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#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;
+
+ mutex_lock(&chip->mutex);
+ value->value.enumerated.item[0] = data->output_sel;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mutex);
+
+ 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;
+
+ mutex_lock(&chip->mutex);
+ val->value.integer.value[0] =
+ !(data->cs4245_shadow[CS4245_DAC_CTRL_1] & CS4245_MUTE_DAC);
+ mutex_unlock(&chip->mutex);
+ 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;
+ mutex_lock(&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;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ value->value.integer.value[0] = data->input_vol[idx][0];
+ value->value.integer.value[1] = data->input_vol[idx][1];
+ mutex_unlock(&chip->mutex);
+ 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;
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ value->value.enumerated.item[0] = data->input_sel;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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 <clemens@ladisch.de>
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#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, &param);
+ param = 1;
+ hdmi_write_command(chip, 0x74, 1, &param);
+ 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, &param);
+}
+
+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..e951f5478
--- /dev/null
+++ b/sound/pci/oxygen/xonar_lib.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * helper functions for Asus Xonar cards
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ return changed;
+}
diff --git a/sound/pci/oxygen/xonar_pcm179x.c b/sound/pci/oxygen/xonar_pcm179x.c
new file mode 100644
index 000000000..cf801a235
--- /dev/null
+++ b/sound/pci/oxygen/xonar_pcm179x.c
@@ -0,0 +1,1355 @@
+// 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 <clemens@ladisch.de>
+ */
+
+/*
+ * 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 <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&chip->mutex);
+ 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]];
+ mutex_lock(&chip->mutex);
+ changed = offset != data->hp_gain_offset;
+ if (changed) {
+ data->hp_gain_offset = offset;
+ update_pcm1796_volume(chip);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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) {
+ spin_lock_irq(&chip->reg_lock);
+ oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+ mute ? GPIO_INPUT_ROUTE : 0,
+ GPIO_INPUT_ROUTE);
+ spin_unlock_irq(&chip->reg_lock);
+ }
+}
+
+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..8aa92f3e5
--- /dev/null
+++ b/sound/pci/oxygen/xonar_wm87x6.c
@@ -0,0 +1,1333 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * card driver for models with WM8776/WM8766 DACs (Xonar DS/HDAV1.3 Slim)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ */
+
+/*
+ * 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 <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&chip->mutex);
+}
+
+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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+ mutex_lock(&chip->mutex);
+ changed = value != (ctl->private_value & 0xf);
+ if (changed) {
+ ctl->private_value = (ctl->private_value & ~0xf) | value;
+ wm8776_field_set_from_ctl(ctl);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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;
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mutex);
+ 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..5993d86cf
--- /dev/null
+++ b/sound/pci/pcxhr/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-pcxhr-objs := 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..c2e4831c3
--- /dev/null
+++ b/sound/pci/pcxhr/pcxhr.c
@@ -0,0 +1,1662 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * main file with alsa callbacks
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ */
+
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#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 <bollinger@digigram.com>, "
+ "Marc Titinger <titinger@digigram.com>");
+MODULE_DESCRIPTION("Digigram " DRIVER_NAME " " PCXHR_DRIVER_VERSION_STRING);
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram," DRIVER_NAME "}}");
+
+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[] = {
+ { 0x10b5, 0x9656, 0x1369, 0xb001, 0, 0, PCI_ID_VX882HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xb101, 0, 0, PCI_ID_PCX882HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xb201, 0, 0, PCI_ID_VX881HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xb301, 0, 0, PCI_ID_PCX881HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xb021, 0, 0, PCI_ID_VX882E, },
+ { 0x10b5, 0x9056, 0x1369, 0xb121, 0, 0, PCI_ID_PCX882E, },
+ { 0x10b5, 0x9056, 0x1369, 0xb221, 0, 0, PCI_ID_VX881E, },
+ { 0x10b5, 0x9056, 0x1369, 0xb321, 0, 0, PCI_ID_PCX881E, },
+ { 0x10b5, 0x9656, 0x1369, 0xb401, 0, 0, PCI_ID_VX1222HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xb501, 0, 0, PCI_ID_PCX1222HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xb601, 0, 0, PCI_ID_VX1221HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xb701, 0, 0, PCI_ID_PCX1221HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xb421, 0, 0, PCI_ID_VX1222E, },
+ { 0x10b5, 0x9056, 0x1369, 0xb521, 0, 0, PCI_ID_PCX1222E, },
+ { 0x10b5, 0x9056, 0x1369, 0xb621, 0, 0, PCI_ID_VX1221E, },
+ { 0x10b5, 0x9056, 0x1369, 0xb721, 0, 0, PCI_ID_PCX1221E, },
+ { 0x10b5, 0x9056, 0x1369, 0xba01, 0, 0, PCI_ID_VX222HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xba21, 0, 0, PCI_ID_VX222E, },
+ { 0x10b5, 0x9056, 0x1369, 0xbd01, 0, 0, PCI_ID_PCX22HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xbd21, 0, 0, PCI_ID_PCX22E, },
+ { 0x10b5, 0x9056, 0x1369, 0xbc01, 0, 0, PCI_ID_VX222HRMIC, },
+ { 0x10b5, 0x9056, 0x1369, 0xbc21, 0, 0, PCI_ID_VX222E_MIC, },
+ { 0x10b5, 0x9056, 0x1369, 0xbb01, 0, 0, PCI_ID_PCX924HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xbb21, 0, 0, PCI_ID_PCX924E, },
+ { 0x10b5, 0x9056, 0x1369, 0xbf01, 0, 0, PCI_ID_PCX924HRMIC, },
+ { 0x10b5, 0x9056, 0x1369, 0xbf21, 0, 0, PCI_ID_PCX924E_MIC, },
+ { 0x10b5, 0x9656, 0x1369, 0xd001, 0, 0, PCI_ID_VX442HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xd101, 0, 0, PCI_ID_PCX442HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xd021, 0, 0, PCI_ID_VX442E, },
+ { 0x10b5, 0x9056, 0x1369, 0xd121, 0, 0, PCI_ID_PCX442E, },
+ { 0x10b5, 0x9656, 0x1369, 0xd201, 0, 0, PCI_ID_VX822HR, },
+ { 0x10b5, 0x9656, 0x1369, 0xd301, 0, 0, PCI_ID_PCX822HR, },
+ { 0x10b5, 0x9056, 0x1369, 0xd221, 0, 0, PCI_ID_VX822E, },
+ { 0x10b5, 0x9056, 0x1369, 0xd321, 0, 0, PCI_ID_PCX822E, },
+ { 0, }
+};
+
+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))
+
+static int pcxhr_pll_freq_register(unsigned int freq, unsigned int* pllreg,
+ unsigned int* realfreq)
+{
+ unsigned int reg;
+
+ if (freq < 6900 || freq > 110000)
+ return -EINVAL;
+ reg = (28224000 * 2) / freq;
+ reg = (reg - 1) / 2;
+ 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, &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, "pcxhr_sub_set_clock to %dHz (realfreq=%d)\n",
+ 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,
+ "pcxhr_set_stream_state CANNOT be stopped\n");
+ 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<<stream->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 pcxhr_set_stream_state err=%x;\n", 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 pcxhr_set_format() : unknown format\n");
+ 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 pcxhr_set_format err=%x;\n", 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,
+ "pcxhr_update_r_buffer(pcm%c%d) : addr(%p) bytes(%zx) subs(%d)\n",
+ 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<<stream->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
+ mutex_lock(&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) {
+ mutex_unlock(&mgr->setup_mutex);
+ dev_err(&mgr->pci->dev, "pcxhr_start_linked_stream : no pipes\n");
+ return;
+ }
+
+ dev_dbg(&mgr->pci->dev, "pcxhr_start_linked_stream : "
+ "playback_mask=%x capture_mask=%x\n",
+ playback_mask, capture_mask);
+
+ /* synchronous stop of all the pipes concerned */
+ err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0);
+ if (err) {
+ mutex_unlock(&mgr->setup_mutex);
+ dev_err(&mgr->pci->dev, "pcxhr_start_linked_stream : "
+ "error stop pipes (P%x C%x)\n",
+ 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) {
+ mutex_unlock(&mgr->setup_mutex);
+ dev_err(&mgr->pci->dev, "pcxhr_start_linked_stream : "
+ "error start pipes (P%x C%x)\n",
+ playback_mask, capture_mask);
+ return;
+ }
+
+ /* put the streams into the running state now
+ * (increment pointer by interrupt)
+ */
+ mutex_lock(&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;
+ }
+ }
+ }
+ mutex_unlock(&mgr->lock);
+
+ mutex_unlock(&mgr->setup_mutex);
+
+#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 pcxhr_hardware_timer err(%x)\n",
+ 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,
+ "pcxhr_prepare : period_size(%lx) periods(%x) buffer_size(%lx)\n",
+ subs->runtime->period_size, subs->runtime->periods,
+ subs->runtime->buffer_size);
+
+ mutex_lock(&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) */
+
+ mutex_unlock(&mgr->setup_mutex);
+
+ 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;
+
+ mutex_lock(&mgr->setup_mutex);
+
+ /* set up channels */
+ stream->channels = params_channels(hw);
+ /* set up format for the stream */
+ stream->format = params_format(hw);
+
+ mutex_unlock(&mgr->setup_mutex);
+
+ 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;
+
+ mutex_lock(&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, "pcxhr_open playback chip%d subs%d\n",
+ chip->chip_idx, subs->number);
+ stream = &chip->playback_stream[subs->number];
+ } else {
+ dev_dbg(chip->card->dev, "pcxhr_open capture chip%d subs%d\n",
+ 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, "pcxhr_open chip%d subs%d in use\n",
+ chip->chip_idx, subs->number);
+ mutex_unlock(&mgr->setup_mutex);
+ 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) {
+ mutex_unlock(&mgr->setup_mutex);
+ 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 */
+ mutex_unlock(&mgr->setup_mutex);
+ 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++;
+
+ mutex_unlock(&mgr->setup_mutex);
+ 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;
+
+ mutex_lock(&mgr->setup_mutex);
+
+ dev_dbg(chip->card->dev, "pcxhr_close chip%d subs%d\n",
+ 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;
+
+ mutex_unlock(&mgr->setup_mutex);
+
+ 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;
+
+ mutex_lock(&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;
+
+ mutex_unlock(&chip->mgr->lock);
+
+ 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);
+ if ((err = snd_pcm_new(chip->card, name, 0,
+ chip->nb_streams_play,
+ chip->nb_streams_capt, &pcm)) < 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;
+ strcpy(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(sizeof(*chip), GFP_KERNEL);
+ 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 */
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 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 */
+ if ((err = pci_enable_device(pci)) < 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(sizeof(*mgr), GFP_KERNEL);
+ 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 */
+ if ((err = pci_request_regions(pci, card_name)) < 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;
+
+ 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);
+
+ /* ISR lock */
+ mutex_init(&mgr->lock);
+ mutex_init(&mgr->msg_lock);
+
+ /* init setup mutex*/
+ mutex_init(&mgr->setup_mutex);
+
+ 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;
+ }
+
+ strcpy(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);
+
+ if ((err = pcxhr_create(mgr, card, i)) < 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]);
+
+ if ((err = snd_card_register(card)) < 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..1b85200d0
--- /dev/null
+++ b/sound/pci/pcxhr/pcxhr.h
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Digigram pcxhr soundcards
+ *
+ * main header file
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ */
+
+#ifndef __SOUND_PCXHR_H
+#define __SOUND_PCXHR_H
+
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <sound/pcm.h>
+
+#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_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..87d24224c
--- /dev/null
+++ b/sound/pci/pcxhr/pcxhr_core.c
@@ -0,0 +1,1333 @@
+// 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 <alsa@digigram.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#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)
+#undef 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, &reg);
+ 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,
+ &reg);
+ 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, &reg);
+}
+
+/*
+ * 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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)
+{
+ int err;
+
+ mutex_lock(&mgr->msg_lock);
+ err = pcxhr_send_msg_nolock(mgr, rmh);
+ mutex_unlock(&mgr->msg_lock);
+ return err;
+}
+
+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<<audio);
+ }
+ audio_mask>>=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;
+
+ mutex_lock(&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;
+ mutex_unlock(&mgr->msg_lock);
+ 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;
+ }
+ mutex_unlock(&mgr->msg_lock);
+ 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<<stream->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;
+
+ mutex_lock(&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);
+ mutex_unlock(&mgr->lock);
+ 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 <alsa@digigram.com>
+ */
+
+#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..2258bd698
--- /dev/null
+++ b/sound/pci/pcxhr/pcxhr_hwdep.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * hwdep device manager
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/vmalloc.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#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];
+
+ if ((err = pcxhr_create_pcm(chip)) < 0)
+ return err;
+
+ if (card_index == 0) {
+ if ((err = pcxhr_create_mixer(chip->mgr)) < 0)
+ return err;
+ }
+ if ((err = snd_card_register(chip->card)) < 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];
+
+ const struct firmware *fw_entry;
+ 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]);
+ 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);
+ release_firmware(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 <alsa@digigram.com>
+ */
+
+#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..f340458fd
--- /dev/null
+++ b/sound/pci/pcxhr/pcxhr_mix22.c
@@ -0,0 +1,855 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * mixer interface for stereo cards
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/asoundef.h>
+#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;
+}
+
+
+/* calc PLL register */
+/* TODO : there is a very similar fct in pcxhr.c */
+static int hr222_pll_freq_register(unsigned int freq,
+ unsigned int *pllreg,
+ unsigned int *realfreq)
+{
+ unsigned int reg;
+
+ if (freq < 6900 || freq > 219000)
+ 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;
+}
+
+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 = hr222_pll_freq_register(rate, &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",
+ is_capture ? "capture" : "playback", 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->mic_volume;
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->mic_boost;
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->phantom_power;
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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 <alsa@digigram.com>
+ */
+
+#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..aec509461
--- /dev/null
+++ b/sound/pci/pcxhr/pcxhr_mixer.c
@@ -0,0 +1,1246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#define __NO_VERSION__
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * mixer callbacks
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ */
+
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include "pcxhr.h"
+#include "pcxhr_hwdep.h"
+#include "pcxhr_core.h"
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/asoundef.h>
+#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);
+ mutex_lock(&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];
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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);
+ }
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->analog_playback_active[0];
+ ucontrol->value.integer.value[1] = chip->analog_playback_active[1];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&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);
+ }
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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<<idx);
+ /* volume left->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;
+
+ mutex_lock(&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];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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 */
+
+ mutex_lock(&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];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->monitoring_volume[0];
+ ucontrol->value.integer.value[1] = chip->monitoring_volume[1];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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);
+ mutex_lock(&chip->mgr->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->monitoring_active[0];
+ ucontrol->value.integer.value[1] = chip->monitoring_active[1];
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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<<i); /* mask 0x01 and 0x02 */
+ }
+ }
+ if (changed & 0x01)
+ /* update left monitoring volume and mute */
+ pcxhr_update_audio_pipe_level(chip, 0, 0);
+ if (changed & 0x02)
+ /* update right monitoring volume and mute */
+ pcxhr_update_audio_pipe_level(chip, 0, 1);
+
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ return (changed != 0);
+}
+
+static const struct snd_kcontrol_new pcxhr_control_monitor_sw = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitoring Playback Switch",
+ .info = pcxhr_sw_info, /* shared */
+ .get = pcxhr_monitor_sw_get,
+ .put = pcxhr_monitor_sw_put
+};
+
+
+
+/*
+ * audio source select
+ */
+#define PCXHR_SOURCE_AUDIO01_UER 0x000100
+#define PCXHR_SOURCE_AUDIO01_SYNC 0x000200
+#define PCXHR_SOURCE_AUDIO23_UER 0x000400
+#define PCXHR_SOURCE_AUDIO45_UER 0x001000
+#define PCXHR_SOURCE_AUDIO67_UER 0x040000
+
+static int pcxhr_set_audio_source(struct snd_pcxhr* chip)
+{
+ struct pcxhr_rmh rmh;
+ unsigned int mask, reg;
+ unsigned int codec;
+ int err, changed;
+
+ switch (chip->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;
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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;
+ mutex_lock(&mgr->mixer_mutex);
+ if (mgr->use_clock_type != ucontrol->value.enumerated.item[0]) {
+ mutex_lock(&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;
+ }
+ mutex_unlock(&mgr->setup_mutex);
+ ret = 1; /* return 1 even if the set was not done. ok ? */
+ }
+ mutex_unlock(&mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&mgr->mixer_mutex);
+ 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;
+
+ mutex_lock(&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;
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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 */
+ mutex_lock(&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;
+ }
+ }
+ mutex_unlock(&chip->mgr->mixer_mutex);
+ 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 <alsa@digigram.com>
+ */
+
+#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..9a505bae2
--- /dev/null
+++ b/sound/pci/riptide/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-riptide-objs := 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..fcc2073c5
--- /dev/null
+++ b/sound/pci/riptide/riptide.c
@@ -0,0 +1,2197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for the Conexant Riptide Soundchip
+ *
+ * Copyright (c) 2004 Peter Gruber <nokos@gmx.net>
+ */
+/*
+ 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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/gameport.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+#if IS_REACHABLE(CONFIG_GAMEPORT)
+#define SUPPORT_JOYSTICK 1
+#endif
+
+MODULE_AUTHOR("Peter Gruber <nokos@gmx.net>");
+MODULE_DESCRIPTION("riptide");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Conexant,Riptide}}");
+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 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;
+#ifdef CONFIG_PM_SLEEP
+ int in_suspend;
+#endif
+};
+
+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;
+ }
+ }
+ }
+ snd_printdd("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) {
+ snd_printdd("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) {
+ snd_printdd("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;
+ }
+ snd_printdd("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) {
+ snd_printk(KERN_ERR
+ "Riptide: Too many failed cmds, reinitializing\n");
+ if (riptide_reset(cif, NULL) == 0) {
+ cif->errcnt = 0;
+ return -EIO;
+ }
+ }
+ snd_printk(KERN_ERR "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)
+ snd_printdd
+ ("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);
+ snd_printdd
+ ("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;
+
+ snd_printdd("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);
+ snd_printdd("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];
+ snd_printdd("getsourcesink 0x%x 0x%x\n", *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])
+ snd_printdd("rates differ %d %d\n", p[0], p[1]);
+ *rate = (unsigned int)p[0];
+ } else
+ *rate = (unsigned int)p[1];
+ snd_printdd("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;
+
+ snd_printdd
+ ("setsampleformat mixer: %d id: %d channels: %d format: %d\n",
+ 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)) {
+ snd_printdd("setsampleformat failed\n");
+ 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;
+
+ snd_printdd("setsamplerate intdec: %d,%d rate: %d\n", 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) {
+ snd_printdd("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];
+ snd_printdd("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] &&
+ (runtime = substream[i]->runtime) &&
+ (data = runtime->private_data) && 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;
+ snd_printk(KERN_ERR
+ "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);
+ snd_printdd
+ ("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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(riptide_pm, riptide_suspend, riptide_resume);
+#define RIPTIDE_PM_OPS &riptide_pm
+#else
+#define RIPTIDE_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+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) {
+ snd_printk(KERN_ERR
+ "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 {
+ snd_printdd
+ ("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);
+ snd_printdd("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 */
+
+ }
+
+ snd_printdd("Writing Firmware\n");
+ if (!chip->fw_entry) {
+ err = request_firmware(&chip->fw_entry, "riptide.hex",
+ &chip->pci->dev);
+ if (err) {
+ snd_printk(KERN_ERR
+ "Riptide: Firmware not available %d\n", err);
+ return -EIO;
+ }
+ }
+ err = loadfirmware(cif, chip->fw_entry->data, chip->fw_entry->size);
+ if (err) {
+ snd_printk(KERN_ERR
+ "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);
+ snd_printdd("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) {
+ snd_printdd
+ ("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 {
+ snd_printdd("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)
+ snd_printk(KERN_ERR "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;
+
+ snd_printdd("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;
+ }
+ snd_printdd("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;
+ snd_printdd
+ ("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;
+
+ snd_printdd("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);
+ if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ &chip->pci->dev,
+ sizeof(struct sgd) * (DESC_MAX_MASK + 1),
+ sgdlist)) < 0) {
+ snd_printk(KERN_ERR "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(sizeof(struct pcmhw), GFP_KERNEL);
+ 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(sizeof(struct pcmhw), GFP_KERNEL);
+ 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;
+
+ if ((err =
+ snd_pcm_new(chip->card, "RIPTIDE", device, PLAYBACK_SUBSTREAMS, 1,
+ &pcm)) < 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;
+ strcpy(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;
+
+ snd_printdd("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)
+ snd_printdd("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);
+ snd_printdd("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) {
+ if ((cif = kzalloc(sizeof(struct cmdif), GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ cif->hwport = (struct riptideport *)chip->port;
+ spin_lock_init(&cif->lock);
+ chip->cif = cif;
+ }
+ cif->is_reset = 0;
+ if ((err = riptide_reset(cif, chip)) != 0)
+ return err;
+ device_id = chip->device_id;
+ switch (device_id) {
+ case 0x4310:
+ case 0x4320:
+ case 0x4330:
+ snd_printdd("Modem enable?\n");
+ SEND_SETDPLL(cif);
+ break;
+ }
+ snd_printdd("Enabling MPU IRQs\n");
+ if (chip->rmidi)
+ SET_EMPUIRQ(cif->hwport);
+ return err;
+}
+
+static int snd_riptide_free(struct snd_riptide *chip)
+{
+ struct cmdif *cif;
+
+ if (!chip)
+ return 0;
+
+ if ((cif = chip->cif)) {
+ SET_GRESET(cif->hwport);
+ udelay(100);
+ UNSET_GRESET(cif->hwport);
+ kfree(chip->cif);
+ }
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ release_firmware(chip->fw_entry);
+ release_and_free_resource(chip->res_port);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_riptide_dev_free(struct snd_device *device)
+{
+ struct snd_riptide *chip = device->device_data;
+
+ return snd_riptide_free(chip);
+}
+
+static int
+snd_riptide_create(struct snd_card *card, struct pci_dev *pci,
+ struct snd_riptide **rchip)
+{
+ struct snd_riptide *chip;
+ struct riptideport *hwport;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_riptide_dev_free,
+ };
+
+ *rchip = NULL;
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ if (!(chip = kzalloc(sizeof(struct snd_riptide), GFP_KERNEL)))
+ return -ENOMEM;
+
+ 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;
+
+ if ((chip->res_port =
+ request_region(chip->port, 64, "RIPTIDE")) == NULL) {
+ snd_printk(KERN_ERR
+ "Riptide: unable to grab region 0x%lx-0x%lx\n",
+ chip->port, chip->port + 64 - 1);
+ snd_riptide_free(chip);
+ return -EBUSY;
+ }
+ hwport = (struct riptideport *)chip->port;
+ UNSET_AIE(hwport);
+
+ if (request_threaded_irq(pci->irq, snd_riptide_interrupt,
+ riptide_handleirq, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ snd_printk(KERN_ERR "Riptide: unable to grab IRQ %d\n",
+ pci->irq);
+ snd_riptide_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ chip->device_id = pci->device;
+ pci_set_master(pci);
+ if ((err = snd_riptide_initialize(chip)) < 0) {
+ snd_riptide_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_riptide_free(chip);
+ return err;
+ }
+
+ *rchip = chip;
+ 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));
+ if ((cif = chip->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
+ && (data =
+ chip->playback_substream[i]->runtime->private_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)) {
+ 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;
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 0)
+ return err;
+
+ chip->ac97_bus = pbus;
+ ac97.pci = chip->pci;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 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")) {
+ snd_printk(KERN_WARNING
+ "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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+ err = snd_riptide_create(card, pci, &chip);
+ if (err < 0)
+ goto error;
+ card->private_data = chip;
+ err = snd_riptide_pcm(chip, 0);
+ if (err < 0)
+ goto error;
+ err = snd_riptide_mixer(chip);
+ if (err < 0)
+ goto error;
+
+ 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)
+ snd_printk(KERN_WARNING
+ "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)
+ snd_printk(KERN_WARNING
+ "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)
+ snd_printk(KERN_WARNING
+ "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
+
+ strcpy(card->driver, "RIPTIDE");
+ strcpy(card->shortname, "Riptide");
+#ifdef SUPPORT_JOYSTICK
+ snprintf(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
+ snprintf(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)
+ goto error;
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+ error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_card_riptide_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_riptide_ids,
+ .probe = snd_card_riptide_probe,
+ .remove = snd_card_riptide_remove,
+ .driver = {
+ .pm = RIPTIDE_PM_OPS,
+ },
+};
+
+#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..869af8a32
--- /dev/null
+++ b/sound/pci/rme32.c
@@ -0,0 +1,1945 @@
+// 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 <martin-langer@gmx.de>,
+ * Pilo Chambert <pilo.c@wanadoo.fr>
+ *
+ * Thanks to : Anders Torger <torger@ludd.luth.se>,
+ * Henk Hesselink <henk@anda.nl>
+ * 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 <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm-indirect.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+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 <martin-langer@gmx.de>, Pilo Chambert <pilo.c@wanadoo.fr>");
+MODULE_DESCRIPTION("RME Digi32, Digi32/8, Digi32 PRO");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME,Digi32}," "{RME,Digi32/8}," "{RME,Digi32 PRO}}");
+
+/* 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), 0,},
+ {PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32_8), 0,},
+ {PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32_PRO), 0,},
+ {0,}
+};
+
+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,
+ void __user *src, unsigned long count)
+{
+ struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+ if (copy_from_user_toio(rme32->iobase + RME32_IO_DATA_BUFFER + pos,
+ src, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_rme32_playback_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *src, unsigned long count)
+{
+ struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+ memcpy_toio(rme32->iobase + RME32_IO_DATA_BUFFER + pos, src, count);
+ return 0;
+}
+
+/* copy callback for halfduplex mode */
+static int snd_rme32_capture_copy(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void __user *dst, unsigned long count)
+{
+ struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+ if (copy_to_user_fromio(dst,
+ rme32->iobase + RME32_IO_DATA_BUFFER + pos,
+ count))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_rme32_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *dst, unsigned long count)
+{
+ struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+ memcpy_fromio(dst, rme32->iobase + RME32_IO_DATA_BUFFER + pos, count);
+ 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;
+ break;
+ }
+ 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;
+ }
+
+ spin_lock_irq(&rme32->lock);
+ if ((rme32->rcreg & RME32_RCR_KMODE) &&
+ (rate = snd_rme32_capture_getrate(rme32, &dummy)) > 0) {
+ /* AutoSync */
+ if ((int)params_rate(params) != rate) {
+ spin_unlock_irq(&rme32->lock);
+ return -EIO;
+ }
+ } else if ((err = snd_rme32_playback_setrate(rme32, params_rate(params))) < 0) {
+ spin_unlock_irq(&rme32->lock);
+ return err;
+ }
+ if ((err = snd_rme32_setformat(rme32, params_format(params))) < 0) {
+ spin_unlock_irq(&rme32->lock);
+ 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) {
+ spin_unlock_irq(&rme32->lock);
+ 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);
+ }
+ spin_unlock_irq(&rme32->lock);
+
+ 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;
+ }
+
+ spin_lock_irq(&rme32->lock);
+ /* enable AutoSync for record-preparing */
+ rme32->wcreg |= RME32_WCR_AUTOSYNC;
+ writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+
+ if ((err = snd_rme32_setformat(rme32, params_format(params))) < 0) {
+ spin_unlock_irq(&rme32->lock);
+ return err;
+ }
+ if ((err = snd_rme32_playback_setrate(rme32, params_rate(params))) < 0) {
+ spin_unlock_irq(&rme32->lock);
+ return err;
+ }
+ if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 0) {
+ if ((int)params_rate(params) != rate) {
+ spin_unlock_irq(&rme32->lock);
+ return -EIO;
+ }
+ if ((isadat && runtime->hw.channels_min == 2) ||
+ (!isadat && runtime->hw.channels_min == 8)) {
+ spin_unlock_irq(&rme32->lock);
+ 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) {
+ spin_unlock_irq(&rme32->lock);
+ return -EBUSY;
+ }
+ }
+ rme32->capture_periodsize =
+ params_period_size(params) << rme32->capture_frlog;
+ spin_unlock_irq(&rme32->lock);
+
+ 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);
+
+ spin_lock_irq(&rme32->lock);
+ if (rme32->playback_substream != NULL) {
+ spin_unlock_irq(&rme32->lock);
+ return -EBUSY;
+ }
+ rme32->wcreg &= ~RME32_WCR_ADAT;
+ writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+ rme32->playback_substream = substream;
+ spin_unlock_irq(&rme32->lock);
+
+ 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;
+ }
+ if ((rme32->rcreg & RME32_RCR_KMODE) &&
+ (rate = snd_rme32_capture_getrate(rme32, &dummy)) > 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);
+
+ spin_lock_irq(&rme32->lock);
+ if (rme32->capture_substream != NULL) {
+ spin_unlock_irq(&rme32->lock);
+ return -EBUSY;
+ }
+ rme32->capture_substream = substream;
+ spin_unlock_irq(&rme32->lock);
+
+ 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;
+ }
+ if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 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);
+
+ spin_lock_irq(&rme32->lock);
+ if (rme32->playback_substream != NULL) {
+ spin_unlock_irq(&rme32->lock);
+ return -EBUSY;
+ }
+ rme32->wcreg |= RME32_WCR_ADAT;
+ writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+ rme32->playback_substream = substream;
+ spin_unlock_irq(&rme32->lock);
+
+ if (rme32->fullduplex_mode)
+ runtime->hw = snd_rme32_adat_fd_info;
+ else
+ runtime->hw = snd_rme32_adat_info;
+ if ((rme32->rcreg & RME32_RCR_KMODE) &&
+ (rate = snd_rme32_capture_getrate(rme32, &dummy)) > 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;
+ if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 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);
+
+ spin_lock_irq(&rme32->lock);
+ if (rme32->capture_substream != NULL) {
+ spin_unlock_irq(&rme32->lock);
+ return -EBUSY;
+ }
+ rme32->capture_substream = substream;
+ spin_unlock_irq(&rme32->lock);
+
+ 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;
+
+ spin_lock_irq(&rme32->lock);
+ rme32->playback_substream = NULL;
+ rme32->playback_periodsize = 0;
+ spdif = (rme32->wcreg & RME32_WCR_ADAT) == 0;
+ spin_unlock_irq(&rme32->lock);
+ 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);
+
+ spin_lock_irq(&rme32->lock);
+ rme32->capture_substream = NULL;
+ rme32->capture_periodsize = 0;
+ spin_unlock_irq(&rme32->lock);
+ return 0;
+}
+
+static int snd_rme32_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+ spin_lock_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);
+ spin_unlock_irq(&rme32->lock);
+ return 0;
+}
+
+static int snd_rme32_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&rme32->lock);
+ 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;
+
+ spin_lock(&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;
+ }
+ spin_unlock(&rme32->lock);
+ 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;
+ spin_lock(&rme32->lock);
+ rec->hw_queue_size = RME32_BUFFER_SIZE;
+ if (rme32->running & (1 << SNDRV_PCM_STREAM_CAPTURE))
+ rec->hw_queue_size -= cprec->hw_ready;
+ spin_unlock(&rme32->lock);
+ 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_user = snd_rme32_playback_copy,
+ .copy_kernel = snd_rme32_playback_copy_kernel,
+ .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_user = snd_rme32_capture_copy,
+ .copy_kernel = snd_rme32_capture_copy_kernel,
+ .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_user = snd_rme32_playback_copy,
+ .copy_kernel = snd_rme32_playback_copy_kernel,
+ .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_user = snd_rme32_capture_copy,
+ .copy_kernel = snd_rme32_capture_copy_kernel,
+ .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(void *private_data)
+{
+ struct rme32 *rme32 = (struct rme32 *) private_data;
+
+ if (rme32 == NULL) {
+ return;
+ }
+ if (rme32->irq >= 0) {
+ snd_rme32_pcm_stop(rme32, 0);
+ free_irq(rme32->irq, (void *) rme32);
+ rme32->irq = -1;
+ }
+ if (rme32->iobase) {
+ iounmap(rme32->iobase);
+ rme32->iobase = NULL;
+ }
+ if (rme32->port) {
+ pci_release_regions(rme32->pci);
+ rme32->port = 0;
+ }
+ pci_disable_device(rme32->pci);
+}
+
+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);
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ if ((err = pci_request_regions(pci, "RME32")) < 0)
+ return err;
+ rme32->port = pci_resource_start(rme32->pci, 0);
+
+ rme32->iobase = ioremap(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 (request_irq(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 */
+ if ((err = snd_pcm_new(rme32->card, "Digi32 IEC958", 0, 1, 1, &rme32->spdif_pcm)) < 0) {
+ return err;
+ }
+ rme32->spdif_pcm->private_data = rme32;
+ rme32->spdif_pcm->private_free = snd_rme32_free_spdif_pcm;
+ strcpy(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 {
+ if ((err = snd_pcm_new(rme32->card, "Digi32 ADAT", 1,
+ 1, 1, &rme32->adat_pcm)) < 0)
+ {
+ return err;
+ }
+ rme32->adat_pcm->private_data = rme32;
+ rme32->adat_pcm->private_free = snd_rme32_free_adat_pcm;
+ strcpy(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 */
+ if ((err = snd_rme32_create_switches(rme32->card, rme32)) < 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);
+
+ spin_lock_irq(&rme32->lock);
+ ucontrol->value.integer.value[0] =
+ rme32->wcreg & RME32_WCR_SEL ? 0 : 1;
+ spin_unlock_irq(&rme32->lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&rme32->lock);
+ 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;
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&rme32->lock);
+ 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;
+
+ spin_lock_irq(&rme32->lock);
+ change = val != (unsigned int)snd_rme32_getinputtype(rme32);
+ snd_rme32_setinputtype(rme32, val);
+ spin_unlock_irq(&rme32->lock);
+ 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);
+
+ spin_lock_irq(&rme32->lock);
+ ucontrol->value.enumerated.item[0] = snd_rme32_getclockmode(rme32);
+ spin_unlock_irq(&rme32->lock);
+ 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;
+ spin_lock_irq(&rme32->lock);
+ change = val != (unsigned int)snd_rme32_getclockmode(rme32);
+ snd_rme32_setclockmode(rme32, val);
+ spin_unlock_irq(&rme32->lock);
+ 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);
+ spin_lock_irq(&rme32->lock);
+ change = val != rme32->wcreg_spdif;
+ rme32->wcreg_spdif = val;
+ spin_unlock_irq(&rme32->lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&rme32->lock);
+ 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++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme32_controls[idx], rme32))) < 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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct 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;
+ if ((err = snd_rme32_create(rme32)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ strcpy(card->driver, "Digi32");
+ switch (rme32->pci->device) {
+ case PCI_DEVICE_ID_RME_DIGI32:
+ strcpy(card->shortname, "RME Digi32");
+ break;
+ case PCI_DEVICE_ID_RME_DIGI32_8:
+ strcpy(card->shortname, "RME Digi32/8");
+ break;
+ case PCI_DEVICE_ID_RME_DIGI32_PRO:
+ strcpy(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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_rme32_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver rme32_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_rme32_ids,
+ .probe = snd_rme32_probe,
+ .remove = snd_rme32_remove,
+};
+
+module_pci_driver(rme32_driver);
diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c
new file mode 100644
index 000000000..84eef6a37
--- /dev/null
+++ b/sound/pci/rme96.c
@@ -0,0 +1,2528 @@
+// 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 <torger@ludd.luth.se>
+ *
+ * Thanks to Henk Hesselink <henk@anda.nl> for the analog volume control
+ * code.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+/* note, two last pcis should be equal, it is not a bug */
+
+MODULE_AUTHOR("Anders Torger <torger@ludd.luth.se>");
+MODULE_DESCRIPTION("RME Digi96, Digi96/8, Digi96/8 PRO, Digi96/8 PST, "
+ "Digi96/8 PAD");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME,Digi96},"
+ "{RME,Digi96/8},"
+ "{RME,Digi96/8 PRO},"
+ "{RME,Digi96/8 PST},"
+ "{RME,Digi96/8 PAD}}");
+
+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 */
+
+#ifdef CONFIG_PM_SLEEP
+ u32 playback_pointer;
+ u32 capture_pointer;
+ void *playback_suspend_buffer;
+ void *capture_suspend_buffer;
+#endif
+
+ 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), 0, },
+ { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8), 0, },
+ { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8_PRO), 0, },
+ { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST), 0, },
+ { 0, }
+};
+
+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,
+ void __user *src, unsigned long count)
+{
+ struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+ return copy_from_user_toio(rme96->iobase + RME96_IO_PLAY_BUFFER + pos,
+ src, count);
+}
+
+static int
+snd_rme96_playback_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *src, unsigned long count)
+{
+ struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+ memcpy_toio(rme96->iobase + RME96_IO_PLAY_BUFFER + pos, src, count);
+ return 0;
+}
+
+static int
+snd_rme96_capture_copy(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void __user *dst, unsigned long count)
+{
+ struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+ return copy_to_user_fromio(dst,
+ rme96->iobase + RME96_IO_REC_BUFFER + pos,
+ count);
+}
+
+static int
+snd_rme96_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *dst, unsigned long count)
+{
+ struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+ memcpy_fromio(dst, rme96->iobase + RME96_IO_REC_BUFFER + pos, count);
+ 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)) > 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;
+
+ spin_lock_irq(&rme96->lock);
+ if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+ snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG &&
+ (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+ {
+ /* slave clock */
+ if ((int)params_rate(params) != rate) {
+ err = -EIO;
+ goto error;
+ }
+ } else {
+ err = snd_rme96_playback_setrate(rme96, params_rate(params));
+ if (err < 0)
+ goto error;
+ 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:
+ spin_unlock_irq(&rme96->lock);
+ 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;
+
+ spin_lock_irq(&rme96->lock);
+ if ((err = snd_rme96_capture_setformat(rme96, params_format(params))) < 0) {
+ spin_unlock_irq(&rme96->lock);
+ return err;
+ }
+ if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) {
+ if ((err = snd_rme96_capture_analog_setrate(rme96,
+ params_rate(params))) < 0)
+ {
+ spin_unlock_irq(&rme96->lock);
+ return err;
+ }
+ } else if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0) {
+ if ((int)params_rate(params) != rate) {
+ spin_unlock_irq(&rme96->lock);
+ return -EIO;
+ }
+ if ((isadat && runtime->hw.channels_min == 2) ||
+ (!isadat && runtime->hw.channels_min == 8))
+ {
+ spin_unlock_irq(&rme96->lock);
+ 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)
+ {
+ spin_unlock_irq(&rme96->lock);
+ return -EBUSY;
+ }
+ }
+ rme96->capture_periodsize =
+ params_period_size(params) << rme96->capture_frlog;
+ snd_rme96_set_period_properties(rme96, rme96->capture_periodsize);
+ spin_unlock_irq(&rme96->lock);
+
+ 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);
+ if ((size = rme96->playback_periodsize) != 0 ||
+ (size = rme96->capture_periodsize) != 0)
+ 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);
+ spin_lock_irq(&rme96->lock);
+ if (rme96->playback_substream) {
+ spin_unlock_irq(&rme96->lock);
+ return -EBUSY;
+ }
+ rme96->wcreg &= ~RME96_WCR_ADAT;
+ writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+ rme96->playback_substream = substream;
+ spin_unlock_irq(&rme96->lock);
+
+ 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)) > 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)) > 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;
+ }
+
+ spin_lock_irq(&rme96->lock);
+ if (rme96->capture_substream) {
+ spin_unlock_irq(&rme96->lock);
+ return -EBUSY;
+ }
+ rme96->capture_substream = substream;
+ spin_unlock_irq(&rme96->lock);
+
+ 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);
+ spin_lock_irq(&rme96->lock);
+ if (rme96->playback_substream) {
+ spin_unlock_irq(&rme96->lock);
+ return -EBUSY;
+ }
+ rme96->wcreg |= RME96_WCR_ADAT;
+ writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+ rme96->playback_substream = substream;
+ spin_unlock_irq(&rme96->lock);
+
+ 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)) > 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;
+ }
+ if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 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;
+ }
+
+ spin_lock_irq(&rme96->lock);
+ if (rme96->capture_substream) {
+ spin_unlock_irq(&rme96->lock);
+ return -EBUSY;
+ }
+ rme96->capture_substream = substream;
+ spin_unlock_irq(&rme96->lock);
+
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&rme96->lock);
+ 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);
+
+ spin_lock_irq(&rme96->lock);
+ if (RME96_ISRECORDING(rme96)) {
+ snd_rme96_trigger(rme96, RME96_STOP_CAPTURE);
+ }
+ rme96->capture_substream = NULL;
+ rme96->capture_periodsize = 0;
+ spin_unlock_irq(&rme96->lock);
+ return 0;
+}
+
+static int
+snd_rme96_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+ spin_lock_irq(&rme96->lock);
+ if (RME96_ISPLAYING(rme96)) {
+ snd_rme96_trigger(rme96, RME96_STOP_PLAYBACK);
+ }
+ writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
+ spin_unlock_irq(&rme96->lock);
+ return 0;
+}
+
+static int
+snd_rme96_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+ spin_lock_irq(&rme96->lock);
+ if (RME96_ISRECORDING(rme96)) {
+ snd_rme96_trigger(rme96, RME96_STOP_CAPTURE);
+ }
+ writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);
+ spin_unlock_irq(&rme96->lock);
+ 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_user = snd_rme96_playback_copy,
+ .copy_kernel = snd_rme96_playback_copy_kernel,
+ .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_user = snd_rme96_capture_copy,
+ .copy_kernel = snd_rme96_capture_copy_kernel,
+ .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_user = snd_rme96_playback_copy,
+ .copy_kernel = snd_rme96_playback_copy_kernel,
+ .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_user = snd_rme96_capture_copy,
+ .copy_kernel = snd_rme96_capture_copy_kernel,
+ .mmap = snd_pcm_lib_mmap_iomem,
+};
+
+static void
+snd_rme96_free(void *private_data)
+{
+ struct rme96 *rme96 = (struct rme96 *)private_data;
+
+ if (!rme96)
+ return;
+
+ 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);
+ free_irq(rme96->irq, (void *)rme96);
+ rme96->irq = -1;
+ }
+ if (rme96->iobase) {
+ iounmap(rme96->iobase);
+ rme96->iobase = NULL;
+ }
+ if (rme96->port) {
+ pci_release_regions(rme96->pci);
+ rme96->port = 0;
+ }
+#ifdef CONFIG_PM_SLEEP
+ vfree(rme96->playback_suspend_buffer);
+ vfree(rme96->capture_suspend_buffer);
+#endif
+ pci_disable_device(rme96->pci);
+}
+
+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);
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ if ((err = pci_request_regions(pci, "RME96")) < 0)
+ return err;
+ rme96->port = pci_resource_start(rme96->pci, 0);
+
+ rme96->iobase = ioremap(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 -ENOMEM;
+ }
+
+ if (request_irq(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 */
+ if ((err = snd_pcm_new(rme96->card, "Digi96 IEC958", 0,
+ 1, 1, &rme96->spdif_pcm)) < 0)
+ {
+ return err;
+ }
+ rme96->spdif_pcm->private_data = rme96;
+ rme96->spdif_pcm->private_free = snd_rme96_free_spdif_pcm;
+ strcpy(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 {
+ if ((err = snd_pcm_new(rme96->card, "Digi96 ADAT", 1,
+ 1, 1, &rme96->adat_pcm)) < 0)
+ {
+ return err;
+ }
+ rme96->adat_pcm->private_data = rme96;
+ rme96->adat_pcm->private_free = snd_rme96_free_adat_pcm;
+ strcpy(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 */
+ if ((err = snd_rme96_create_switches(rme96->card, rme96)) < 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);
+
+ spin_lock_irq(&rme96->lock);
+ ucontrol->value.integer.value[0] = rme96->wcreg & RME96_WCR_SEL ? 0 : 1;
+ spin_unlock_irq(&rme96->lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&rme96->lock);
+ 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;
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&rme96->lock);
+ 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;
+ }
+ }
+
+ spin_lock_irq(&rme96->lock);
+ change = (int)val != snd_rme96_getinputtype(rme96);
+ snd_rme96_setinputtype(rme96, val);
+ spin_unlock_irq(&rme96->lock);
+ 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);
+
+ spin_lock_irq(&rme96->lock);
+ ucontrol->value.enumerated.item[0] = snd_rme96_getclockmode(rme96);
+ spin_unlock_irq(&rme96->lock);
+ 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;
+ spin_lock_irq(&rme96->lock);
+ change = (int)val != snd_rme96_getclockmode(rme96);
+ snd_rme96_setclockmode(rme96, val);
+ spin_unlock_irq(&rme96->lock);
+ 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);
+
+ spin_lock_irq(&rme96->lock);
+ ucontrol->value.enumerated.item[0] = snd_rme96_getattenuation(rme96);
+ spin_unlock_irq(&rme96->lock);
+ 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;
+ spin_lock_irq(&rme96->lock);
+
+ change = (int)val != snd_rme96_getattenuation(rme96);
+ snd_rme96_setattenuation(rme96, val);
+ spin_unlock_irq(&rme96->lock);
+ 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);
+
+ spin_lock_irq(&rme96->lock);
+ ucontrol->value.enumerated.item[0] = snd_rme96_getmontracks(rme96);
+ spin_unlock_irq(&rme96->lock);
+ 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;
+ spin_lock_irq(&rme96->lock);
+ change = (int)val != snd_rme96_getmontracks(rme96);
+ snd_rme96_setmontracks(rme96, val);
+ spin_unlock_irq(&rme96->lock);
+ 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);
+ spin_lock_irq(&rme96->lock);
+ change = val != rme96->wcreg_spdif;
+ rme96->wcreg_spdif = val;
+ spin_unlock_irq(&rme96->lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&rme96->lock);
+ 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);
+
+ spin_lock_irq(&rme96->lock);
+ u->value.integer.value[0] = rme96->vol[0];
+ u->value.integer.value[1] = rme96->vol[1];
+ spin_unlock_irq(&rme96->lock);
+
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&rme96->lock);
+
+ 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++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme96_controls[idx], rme96))) < 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++)
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_rme96_controls[idx], rme96))) < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Card initialisation
+ */
+
+#ifdef CONFIG_PM_SLEEP
+
+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 SIMPLE_DEV_PM_OPS(rme96_pm, rme96_suspend, rme96_resume);
+#define RME96_PM_OPS &rme96_pm
+#else
+#define RME96_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct 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)
+ goto free_card;
+
+#ifdef CONFIG_PM_SLEEP
+ rme96->playback_suspend_buffer = vmalloc(RME96_BUFFER_SIZE);
+ if (!rme96->playback_suspend_buffer) {
+ err = -ENOMEM;
+ goto free_card;
+ }
+ rme96->capture_suspend_buffer = vmalloc(RME96_BUFFER_SIZE);
+ if (!rme96->capture_suspend_buffer) {
+ err = -ENOMEM;
+ goto free_card;
+ }
+#endif
+
+ strcpy(card->driver, "Digi96");
+ switch (rme96->pci->device) {
+ case PCI_DEVICE_ID_RME_DIGI96:
+ strcpy(card->shortname, "RME Digi96");
+ break;
+ case PCI_DEVICE_ID_RME_DIGI96_8:
+ strcpy(card->shortname, "RME Digi96/8");
+ break;
+ case PCI_DEVICE_ID_RME_DIGI96_8_PRO:
+ strcpy(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) {
+ strcpy(card->shortname, "RME Digi96/8 PAD");
+ } else {
+ strcpy(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)
+ goto free_card;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+free_card:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_rme96_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver rme96_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_rme96_ids,
+ .probe = snd_rme96_probe,
+ .remove = snd_rme96_remove,
+ .driver = {
+ .pm = RME96_PM_OPS,
+ },
+};
+
+module_pci_driver(rme96_driver);
diff --git a/sound/pci/rme9652/Makefile b/sound/pci/rme9652/Makefile
new file mode 100644
index 000000000..a3351447d
--- /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 <perex@perex.cz>
+#
+
+snd-rme9652-objs := rme9652.o
+snd-hdsp-objs := hdsp.o
+snd-hdspm-objs := 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..954347424
--- /dev/null
+++ b/sound/pci/rme9652/hdsp.c
@@ -0,0 +1,5391 @@
+// 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 <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/math64.h>
+#include <linux/vmalloc.h>
+#include <linux/io.h>
+#include <linux/nospec.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/rawmidi.h>
+#include <sound/hwdep.h>
+#include <sound/initval.h>
+#include <sound/hdsp.h>
+
+#include <asm/byteorder.h>
+#include <asm/current.h>
+
+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 <paul@linuxaudiosystems.com>, Marcus Andersson, Thomas Charbonnel <thomas@undata.org>");
+MODULE_DESCRIPTION("RME Hammerfall DSP");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME Hammerfall-DSP},"
+ "{RME HDSP-9652},"
+ "{RME HDSP-9632}}");
+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;
+
+ 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 int snd_hammerfall_get_buffer(struct pci_dev *pci, struct snd_dma_buffer *dmab, size_t size)
+{
+ return snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, size, dmab);
+}
+
+static void snd_hammerfall_free_buffer(struct snd_dma_buffer *dmab, struct pci_dev *pci)
+{
+ if (dmab->area)
+ snd_dma_free_pages(dmab);
+}
+
+
+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;
+ unsigned long flags;
+ 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");
+ spin_lock_irqsave(&hdsp->lock, flags);
+ snd_hdsp_set_defaults(hdsp);
+ spin_unlock_irqrestore(&hdsp->lock, flags);
+ }
+
+ 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)
+{
+ unsigned long flags;
+ int ret = 1;
+
+ spin_lock_irqsave(&hdsp->lock, flags);
+ if ((hdsp->playback_pid != hdsp->capture_pid) &&
+ (hdsp->playback_pid >= 0) && (hdsp->capture_pid >= 0))
+ ret = 0;
+ spin_unlock_irqrestore(&hdsp->lock, flags);
+ 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;
+
+ spin_lock_irq(&s->lock);
+
+ 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);
+
+ spin_unlock_irq(&s->lock);
+
+ 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)
+{
+ while (snd_hdsp_midi_input_available (hdsp, id))
+ snd_hdsp_midi_read_byte (hdsp, id);
+}
+
+static int snd_hdsp_midi_output_write (struct hdsp_midi *hmidi)
+{
+ unsigned long flags;
+ int n_pending;
+ int to_write;
+ int i;
+ unsigned char buf[128];
+
+ /* Output is not interrupt driven */
+
+ spin_lock_irqsave (&hmidi->lock, flags);
+ if (hmidi->output) {
+ if (!snd_rawmidi_transmit_empty (hmidi->output)) {
+ if ((n_pending = snd_hdsp_midi_output_possible (hmidi->hdsp, hmidi->id)) > 0) {
+ if (n_pending > (int)sizeof (buf))
+ n_pending = sizeof (buf);
+
+ if ((to_write = snd_rawmidi_transmit (hmidi->output, buf, n_pending)) > 0) {
+ for (i = 0; i < to_write; ++i)
+ snd_hdsp_midi_write_byte (hmidi->hdsp, hmidi->id, buf[i]);
+ }
+ }
+ }
+ }
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+ 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 */
+ unsigned long flags;
+ int n_pending;
+ int i;
+
+ spin_lock_irqsave (&hmidi->lock, flags);
+ if ((n_pending = snd_hdsp_midi_input_available (hmidi->hdsp, hmidi->id)) > 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);
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+ 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;
+ unsigned long flags;
+ u32 ie;
+
+ hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+ hdsp = hmidi->hdsp;
+ ie = hmidi->id ? HDSP_Midi1InterruptEnable : HDSP_Midi0InterruptEnable;
+ spin_lock_irqsave (&hdsp->lock, flags);
+ 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);
+ spin_unlock_irqrestore (&hdsp->lock, flags);
+}
+
+static void snd_hdsp_midi_output_timer(struct timer_list *t)
+{
+ struct hdsp_midi *hmidi = from_timer(hmidi, t, timer);
+ unsigned long flags;
+
+ snd_hdsp_midi_output_write(hmidi);
+ spin_lock_irqsave (&hmidi->lock, flags);
+
+ /* 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);
+
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+}
+
+static void snd_hdsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ struct hdsp_midi *hmidi;
+ unsigned long flags;
+
+ hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+ spin_lock_irqsave (&hmidi->lock, flags);
+ 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)
+ del_timer (&hmidi->timer);
+ }
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ snd_hdsp_flush_midi_input (hmidi->hdsp, hmidi->id);
+ hmidi->input = substream;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ hmidi->output = substream;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ hmidi->input = NULL;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ hmidi->output = NULL;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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);
+ spin_lock_irq(&hdsp->lock);
+ change = val != hdsp->creg_spdif;
+ hdsp->creg_spdif = val;
+ spin_unlock_irq(&hdsp->lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&hdsp->lock);
+ 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);
+ spin_lock_irq(&hdsp->lock);
+ change = val != hdsp_spdif_in(hdsp);
+ if (change)
+ hdsp_set_spdif_input(hdsp, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+
+ spin_lock_irq(&hdsp->lock);
+ ucontrol->value.integer.value[0] = hdsp_toggle_setting(hdsp, regmask);
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ change = (int) val != hdsp_toggle_setting(hdsp, regmask);
+ if (change)
+ hdsp_set_toggle_setting(hdsp, regmask, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ }
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_clock_source(hdsp))
+ change = (hdsp_set_clock_source(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_da_gain(hdsp))
+ change = (hdsp_set_da_gain(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_ad_gain(hdsp))
+ change = (hdsp_set_ad_gain(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_phone_gain(hdsp))
+ change = (hdsp_set_phone_gain(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ change = (int)val != hdsp_pref_sync_ref(hdsp);
+ hdsp_set_pref_sync_ref(hdsp, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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);
+
+ spin_lock_irq(&hdsp->lock);
+ ucontrol->value.integer.value[0] = hdsp->precise_ptr;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ change = (int)val != hdsp->precise_ptr;
+ hdsp_set_precise_pointer(hdsp, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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);
+
+ spin_lock_irq(&hdsp->lock);
+ ucontrol->value.integer.value[0] = hdsp->use_midi_work;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ change = (int)val != hdsp->use_midi_work;
+ hdsp_set_use_midi_work(hdsp, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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);
+
+ spin_lock_irq(&hdsp->lock);
+ ucontrol->value.integer.value[2] = hdsp_read_gain (hdsp, addr);
+ spin_unlock_irq(&hdsp->lock);
+ 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];
+
+ spin_lock_irq(&hdsp->lock);
+ change = gain != hdsp_read_gain(hdsp, addr);
+ if (change)
+ hdsp_write_gain(hdsp, addr, gain);
+ spin_unlock_irq(&hdsp->lock);
+ 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];
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_dds_offset(hdsp))
+ change = (hdsp_set_dds_offset(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_rpm_input12(hdsp))
+ change = (hdsp_set_rpm_input12(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ if (val != hdsp_rpm_input34(hdsp))
+ change = (hdsp_set_rpm_input34(hdsp, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ change = (int)val != hdsp_rpm_bypass(hdsp);
+ hdsp_set_rpm_bypass(hdsp, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ spin_lock_irq(&hdsp->lock);
+ change = (int)val != hdsp_rpm_disconnect(hdsp);
+ hdsp_set_rpm_disconnect(hdsp, val);
+ spin_unlock_irq(&hdsp->lock);
+ 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 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, kctl = 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++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_hdsp_controls[idx], hdsp))) < 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;
+ if ((err = snd_ctl_add (card, kctl = snd_ctl_new1(&snd_hdsp_adat_sync_check, hdsp))))
+ 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;
+ if ((err = snd_ctl_add (card, kctl = snd_ctl_new1(&snd_hdsp_adat_sync_check, hdsp))))
+ 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++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_hdsp_9632_controls[idx], hdsp))) < 0)
+ return err;
+ }
+ }
+
+ /* AEB control for H96xx card */
+ if (hdsp->io_type == H9632 || hdsp->io_type == H9652) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_hdsp_96xx_aeb, hdsp))) < 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", hdsp->use_midi_work ? "on" : "off");
+
+ 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", hdsp->precise_ptr ? "on" : "off");
+ snd_iprintf(buffer, "Line out: %s\n", (hdsp->control_register & HDSP_LineOut) ? "on" : "off");
+
+ 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",
+ hdsp_toggle_setting(hdsp, HDSP_XLRBreakoutCable) ?
+ "yes" : "no");
+
+ 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 void snd_hdsp_free_buffers(struct hdsp *hdsp)
+{
+ snd_hammerfall_free_buffer(&hdsp->capture_dma_buf, hdsp->pci);
+ snd_hammerfall_free_buffer(&hdsp->playback_dma_buf, hdsp->pci);
+}
+
+static int snd_hdsp_initialize_memory(struct hdsp *hdsp)
+{
+ unsigned long pb_bus, cb_bus;
+
+ if (snd_hammerfall_get_buffer(hdsp->pci, &hdsp->capture_dma_buf, HDSP_DMA_AREA_BYTES) < 0 ||
+ snd_hammerfall_get_buffer(hdsp->pci, &hdsp->playback_dma_buf, HDSP_DMA_AREA_BYTES) < 0) {
+ if (hdsp->capture_dma_buf.area)
+ snd_dma_free_pages(&hdsp->capture_dma_buf);
+ dev_err(hdsp->card->dev,
+ "%s: no buffers available\n", hdsp->card_name);
+ return -ENOMEM;
+ }
+
+ /* Align to bus-space 64K boundary */
+
+ cb_bus = ALIGN(hdsp->capture_dma_buf.addr, 0x10000ul);
+ pb_bus = ALIGN(hdsp->playback_dma_buf.addr, 0x10000ul);
+
+ /* Tell the card where it is */
+
+ hdsp_write(hdsp, HDSP_inputBufferAddress, cb_bus);
+ hdsp_write(hdsp, HDSP_outputBufferAddress, pb_bus);
+
+ hdsp->capture_buffer = hdsp->capture_dma_buf.area + (cb_bus - hdsp->capture_dma_buf.addr);
+ hdsp->playback_buffer = hdsp->playback_dma_buf.area + (pb_bus - hdsp->playback_dma_buf.addr);
+
+ 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;
+
+ if ((mapped_channel = hdsp->channel_map[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,
+ void __user *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_user(channel_buf + pos, src, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_hdsp_playback_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *src, 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;
+ memcpy(channel_buf + pos, src, count);
+ return 0;
+}
+
+static int snd_hdsp_capture_copy(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void __user *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_user(dst, channel_buf + pos, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_hdsp_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *dst, 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;
+ memcpy(dst, channel_buf + pos, count);
+ 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;
+
+ spin_lock_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) {
+ spin_unlock_irq(&hdsp->lock);
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+ return -EBUSY;
+ }
+
+ if (params_period_size(params) != hdsp->period_bytes / 4) {
+ spin_unlock_irq(&hdsp->lock);
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+ return -EBUSY;
+ }
+
+ /* We're fine. */
+
+ spin_unlock_irq(&hdsp->lock);
+ return 0;
+
+ } else {
+ spin_unlock_irq(&hdsp->lock);
+ }
+
+ /* how to make sure that the rate matches an externally-set one ?
+ */
+
+ spin_lock_irq(&hdsp->lock);
+ if (! hdsp->clock_source_locked) {
+ if ((err = hdsp_set_rate(hdsp, params_rate(params), 0)) < 0) {
+ spin_unlock_irq(&hdsp->lock);
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+ return err;
+ }
+ }
+ spin_unlock_irq(&hdsp->lock);
+
+ if ((err = hdsp_set_interrupt_interval(hdsp, params_period_size(params))) < 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;
+
+ spin_lock(&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();
+ spin_unlock(&hdsp->lock);
+ 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;
+ spin_unlock(&hdsp->lock);
+
+ 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;
+
+ spin_lock_irq(&hdsp->lock);
+ if (!hdsp->running)
+ hdsp_reset_hw_pointer(hdsp);
+ spin_unlock_irq(&hdsp->lock);
+ 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 const unsigned int hdsp_9632_sample_rates[] = { 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000 };
+
+static const struct snd_pcm_hw_constraint_list hdsp_hw_constraints_9632_sample_rates = {
+ .count = ARRAY_SIZE(hdsp_9632_sample_rates),
+ .list = hdsp_9632_sample_rates,
+ .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;
+
+ spin_lock_irq(&hdsp->lock);
+
+ snd_pcm_set_sync(substream);
+
+ runtime->hw = snd_hdsp_playback_subinfo;
+ runtime->dma_area = hdsp->playback_buffer;
+ runtime->dma_bytes = HDSP_DMA_AREA_BYTES;
+
+ hdsp->playback_pid = current->pid;
+ hdsp->playback_substream = substream;
+
+ spin_unlock_irq(&hdsp->lock);
+
+ 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_KNOT;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hdsp_hw_constraints_9632_sample_rates);
+ }
+ 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);
+
+ spin_lock_irq(&hdsp->lock);
+
+ hdsp->playback_pid = -1;
+ hdsp->playback_substream = NULL;
+
+ spin_unlock_irq(&hdsp->lock);
+
+ 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;
+
+ spin_lock_irq(&hdsp->lock);
+
+ snd_pcm_set_sync(substream);
+
+ runtime->hw = snd_hdsp_capture_subinfo;
+ runtime->dma_area = hdsp->capture_buffer;
+ runtime->dma_bytes = HDSP_DMA_AREA_BYTES;
+
+ hdsp->capture_pid = current->pid;
+ hdsp->capture_substream = substream;
+
+ spin_unlock_irq(&hdsp->lock);
+
+ 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_KNOT;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hdsp_hw_constraints_9632_sample_rates);
+ }
+ 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);
+
+ spin_lock_irq(&hdsp->lock);
+
+ hdsp->capture_pid = -1;
+ hdsp->capture_substream = NULL;
+
+ spin_unlock_irq(&hdsp->lock);
+ 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;
+ unsigned long flags;
+ 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));
+ spin_lock_irqsave(&hdsp->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&hdsp->lock, flags);
+ 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) {
+ if ((err = hdsp_get_iobox_version(hdsp)) < 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 ((err = 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;
+
+ if ((err = snd_hdsp_load_firmware_from_cache(hdsp)) < 0)
+ return err;
+
+ if (!(hdsp->state & HDSP_InitializationComplete)) {
+ if ((err = snd_hdsp_enable_io(hdsp)) < 0)
+ return err;
+
+ snd_hdsp_initialize_channels(hdsp);
+ snd_hdsp_initialize_midi_flush(hdsp);
+
+ if ((err = snd_hdsp_create_alsa_devices(hdsp->card, hdsp)) < 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_user = snd_hdsp_playback_copy,
+ .copy_kernel = snd_hdsp_playback_copy_kernel,
+ .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_user = snd_hdsp_capture_copy,
+ .copy_kernel = snd_hdsp_capture_copy_kernel,
+};
+
+static int snd_hdsp_create_hwdep(struct snd_card *card, struct hdsp *hdsp)
+{
+ struct snd_hwdep *hw;
+ int err;
+
+ if ((err = snd_hwdep_new(card, "HDSP hwdep", 0, &hw)) < 0)
+ return err;
+
+ hdsp->hwdep = hw;
+ hw->private_data = hdsp;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(card, hdsp->card_name, 0, 1, 1, &pcm)) < 0)
+ return err;
+
+ hdsp->pcm = pcm;
+ pcm->private_data = hdsp;
+ strcpy(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;
+
+ 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;
+ 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;
+
+ if ((err = snd_hdsp_create_pcm(card, hdsp)) < 0) {
+ dev_err(card->dev,
+ "Error creating pcm interface\n");
+ return err;
+ }
+
+
+ if ((err = snd_hdsp_create_midi(card, hdsp, 0)) < 0) {
+ dev_err(card->dev,
+ "Error creating first midi interface\n");
+ return err;
+ }
+
+ if (hdsp->io_type == Digiface || hdsp->io_type == H9652) {
+ if ((err = snd_hdsp_create_midi(card, hdsp, 1)) < 0) {
+ dev_err(card->dev,
+ "Error creating second midi interface\n");
+ return err;
+ }
+ }
+
+ if ((err = snd_hdsp_create_controls(card, hdsp)) < 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;
+
+ if ((err = snd_hdsp_set_defaults(hdsp)) < 0) {
+ dev_err(card->dev,
+ "Error setting default values\n");
+ return err;
+ }
+
+ if (!(hdsp->state & HDSP_InitializationComplete)) {
+ strcpy(card->shortname, "Hammerfall DSP");
+ sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name,
+ hdsp->port, hdsp->irq);
+
+ if ((err = snd_card_register(card)) < 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) {
+ if ((err = hdsp_get_iobox_version(hdsp)) < 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;
+
+ if ((err = snd_hdsp_load_firmware_from_cache(hdsp)) < 0)
+ return err;
+
+ if (!(hdsp->state & HDSP_InitializationComplete)) {
+ if ((err = snd_hdsp_enable_io(hdsp)) < 0)
+ return err;
+
+ if ((err = snd_hdsp_create_hwdep(hdsp->card, hdsp)) < 0) {
+ dev_err(hdsp->card->dev,
+ "error creating hwdep device\n");
+ return err;
+ }
+ snd_hdsp_initialize_channels(hdsp);
+ snd_hdsp_initialize_midi_flush(hdsp);
+ if ((err = snd_hdsp_create_alsa_devices(hdsp->card, hdsp)) < 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);
+
+ strcpy(card->driver, "H-DSP");
+ strcpy(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;
+ }
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ pci_set_master(hdsp->pci);
+
+ if ((err = pci_request_regions(pci, "hdsp")) < 0)
+ return err;
+ hdsp->port = pci_resource_start(pci, 0);
+ if ((hdsp->iobase = ioremap(hdsp->port, HDSP_IO_EXTENT)) == NULL) {
+ 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 (request_irq(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;
+
+ if ((err = snd_hdsp_initialize_memory(hdsp)) < 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) {
+ if ((err = hdsp_request_fw_loader(hdsp)) < 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");
+ if ((err = snd_hdsp_create_hwdep(card, hdsp)) < 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;
+ }
+ }
+
+ if ((err = snd_hdsp_enable_io(hdsp)) != 0)
+ return err;
+
+ if (is_9652)
+ hdsp->io_type = H9652;
+
+ if (is_9632)
+ hdsp->io_type = H9632;
+
+ if ((err = snd_hdsp_create_hwdep(card, hdsp)) < 0)
+ return err;
+
+ snd_hdsp_initialize_channels(hdsp);
+ snd_hdsp_initialize_midi_flush(hdsp);
+
+ hdsp->state |= HDSP_FirmwareLoaded;
+
+ if ((err = snd_hdsp_create_alsa_devices(card, hdsp)) < 0)
+ return err;
+
+ return 0;
+}
+
+static int snd_hdsp_free(struct hdsp *hdsp)
+{
+ 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);
+ }
+
+ if (hdsp->irq >= 0)
+ free_irq(hdsp->irq, (void *)hdsp);
+
+ snd_hdsp_free_buffers(hdsp);
+
+ release_firmware(hdsp->firmware);
+ vfree(hdsp->fw_uploaded);
+ iounmap(hdsp->iobase);
+
+ if (hdsp->port)
+ pci_release_regions(hdsp->pci);
+
+ if (pci_is_enabled(hdsp->pci))
+ pci_disable_device(hdsp->pci);
+ return 0;
+}
+
+static void snd_hdsp_card_free(struct snd_card *card)
+{
+ struct hdsp *hdsp = card->private_data;
+
+ if (hdsp)
+ snd_hdsp_free(hdsp);
+}
+
+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_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 free_card;
+
+ strcpy(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) {
+free_card:
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_hdsp_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver hdsp_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_hdsp_ids,
+ .probe = snd_hdsp_probe,
+ .remove = snd_hdsp_remove,
+};
+
+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..51c3c6a08
--- /dev/null
+++ b/sound/pci/rme9652/hdspm.c
@@ -0,0 +1,6979 @@
+// 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
+ * <remy.bruno@trinnov.com>
+ *
+ * Modified 2009-04-13 for proper metering by Florian Faber
+ * <faber@faberman.de>
+ *
+ * Modified 2009-04-14 for native float support by Florian Faber
+ * <faber@faberman.de>
+ *
+ * Modified 2009-04-26 fixed bug in rms metering by Florian Faber
+ * <faber@faberman.de>
+ *
+ * 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: . : <MADI> HDSPM_DoubleSpeed
+ * :x . : . : . : . : <MADI> HDSPM_QuadSpeed
+ * : . 3 : . 10: 2 . : . : HDSPM_SyncRefMask :
+ * : . : . x: . : . : HDSPM_SyncRef0
+ * : . : . x : . : . : HDSPM_SyncRef1
+ * : . : . : x . : . : <AES32> HDSPM_SyncRef2
+ * : . x : . : . : . : <AES32> HDSPM_SyncRef3
+ * : . : . 10: . : . : <MADI> sync ref: 0:WC, 1:Madi, 2:TCO, 3:SyncIn
+ * : . 3 : . 10: 2 . : . : <AES32> 0:WC, 1:AES1 ... 8:AES8, 9: TCO, 10:SyncIn?
+ * : . x : . : . : . : <MADIe> HDSPe_FLOAT_FORMAT
+ * : . : . : x . : . : <MADI> HDSPM_InputSelect0 : 0=optical,1=coax
+ * : . : . :x . : . : <MADI> HDSPM_InputSelect1
+ * : . : .x : . : . : <MADI> HDSPM_clr_tms
+ * : . : . : . x : . : <MADI> HDSPM_TX_64ch
+ * : . : . : . x : . : <AES32> HDSPM_Emphasis
+ * : . : . : .x : . : <MADI> HDSPM_AutoInp
+ * : . : . x : . : . : <MADI> HDSPM_SMUX
+ * : . : .x : . : . : <MADI> HDSPM_clr_tms
+ * : . : x. : . : . : <MADI> HDSPM_taxi_reset
+ * : . x: . : . : . : <MADI> HDSPM_LineOut
+ * : . x: . : . : . : <AES32> ??????????????????
+ * : . : x. : . : . : <AES32> HDSPM_WCK48
+ * : . : . : .x : . : <AES32> HDSPM_Dolby
+ * : . : x . : . : . : HDSPM_Midi0InterruptEnable
+ * : . :x . : . : . : HDSPM_Midi1InterruptEnable
+ * : . : x . : . : . : HDSPM_Midi2InterruptEnable
+ * : . x : . : . : . : <MADI> HDSPM_Midi3InterruptEnable
+ * : . x : . : . : . : <AES32> HDSPM_DS_DoubleWire
+ * : .x : . : . : . : <AES32> HDSPM_QS_DoubleWire
+ * : x. : . : . : . : <AES32> HDSPM_QS_QuadWire
+ * : . : . : . x : . : <AES32> 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 <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/math64.h>
+#include <linux/io.h>
+#include <linux/nospec.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/rawmidi.h>
+#include <sound/hwdep.h>
+#include <sound/initval.h>
+
+#include <sound/hdspm.h>
+
+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 <ritsch_AT_iem.at>, "
+ "Paul Davis <paul@linuxaudiosystems.com>, "
+ "Marcus Andersson, Thomas Charbonnel <thomas@undata.org>, "
+ "Remy Bruno <remy.bruno@trinnov.com>, "
+ "Florian Faber <faberman@linuxproaudio.org>, "
+ "Adrian Knoth <adi@drcomp.erfurt.thur.de>"
+);
+MODULE_DESCRIPTION("RME HDSPM");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME HDSPM-MADI}}");
+
+/* --- 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)
+{
+ unsigned long flags;
+ int ret = 1;
+
+ spin_lock_irqsave(&hdspm->lock, flags);
+ if ((hdspm->playback_pid != hdspm->capture_pid) &&
+ (hdspm->playback_pid >= 0) && (hdspm->capture_pid >= 0)) {
+ ret = 0;
+ }
+ spin_unlock_irqrestore(&hdspm->lock, flags);
+ return ret;
+}
+
+/* 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;
+
+ spin_lock_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);
+
+ spin_unlock_irq(&s->lock);
+
+ 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)
+{
+ while (snd_hdspm_midi_input_available (hdspm, id))
+ snd_hdspm_midi_read_byte (hdspm, id);
+}
+
+static int snd_hdspm_midi_output_write (struct hdspm_midi *hmidi)
+{
+ unsigned long flags;
+ int n_pending;
+ int to_write;
+ int i;
+ unsigned char buf[128];
+
+ /* Output is not interrupt driven */
+
+ spin_lock_irqsave (&hmidi->lock, flags);
+ 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]);
+ }
+ }
+ }
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+ 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
+ */
+ unsigned long flags;
+ int n_pending;
+ int i;
+
+ spin_lock_irqsave (&hmidi->lock, flags);
+ 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;
+ spin_unlock_irqrestore(&hmidi->lock, flags);
+
+ spin_lock_irqsave(&hmidi->hdspm->lock, flags);
+ hmidi->hdspm->control_register |= hmidi->ie;
+ hdspm_write(hmidi->hdspm, HDSPM_controlRegister,
+ hmidi->hdspm->control_register);
+ spin_unlock_irqrestore(&hmidi->hdspm->lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ hmidi = substream->rmidi->private_data;
+ hdspm = hmidi->hdspm;
+
+ spin_lock_irqsave (&hdspm->lock, flags);
+ 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);
+ spin_unlock_irqrestore (&hdspm->lock, flags);
+}
+
+static void snd_hdspm_midi_output_timer(struct timer_list *t)
+{
+ struct hdspm_midi *hmidi = from_timer(hmidi, t, timer);
+ unsigned long flags;
+
+ snd_hdspm_midi_output_write(hmidi);
+ spin_lock_irqsave (&hmidi->lock, flags);
+
+ /* 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);
+
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+}
+
+static void
+snd_hdspm_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ struct hdspm_midi *hmidi;
+ unsigned long flags;
+
+ hmidi = substream->rmidi->private_data;
+ spin_lock_irqsave (&hmidi->lock, flags);
+ 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)
+ del_timer (&hmidi->timer);
+ }
+ spin_unlock_irqrestore (&hmidi->lock, flags);
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ snd_hdspm_flush_midi_input (hmidi->hdspm, hmidi->id);
+ hmidi->input = substream;
+ spin_unlock_irq (&hmidi->lock);
+
+ return 0;
+}
+
+static int snd_hdspm_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ struct hdspm_midi *hmidi;
+
+ hmidi = substream->rmidi->private_data;
+ spin_lock_irq (&hmidi->lock);
+ hmidi->output = substream;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ hmidi->input = NULL;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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;
+ spin_lock_irq (&hmidi->lock);
+ hmidi->output = NULL;
+ spin_unlock_irq (&hmidi->lock);
+
+ 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;
+ break;
+ 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;
+ break;
+ 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;
+ break;
+ 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;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Returns the sample rate class for input source <idx> 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;
+ spin_lock_irq(&hdspm->lock);
+ if (val != hdspm_clock_source(hdspm))
+ change = (hdspm_set_clock_source(hdspm, val) == 0) ? 1 : 0;
+ else
+ change = 0;
+ spin_unlock_irq(&hdspm->lock);
+ 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 <pref>. The semantics
+ * of <pref> 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;
+
+ spin_lock_irq(&hdspm->lock);
+ if (val != hdspm_pref_sync_ref(hdspm))
+ change = (0 == hdspm_set_pref_sync_ref(hdspm, val)) ? 1 : 0;
+
+ spin_unlock_irq(&hdspm->lock);
+ 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, xindex) \
+{ .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, xindex) \
+{ .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;
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.integer.value[0] = hdspm_toggle_setting(hdspm, regmask);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+ spin_lock_irq(&hdspm->lock);
+ change = (int) val != hdspm_toggle_setting(hdspm, regmask);
+ hdspm_set_toggle_setting(hdspm, regmask, val);
+ spin_unlock_irq(&hdspm->lock);
+ 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);
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.enumerated.item[0] = hdspm_input_select(hdspm);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+ spin_lock_irq(&hdspm->lock);
+ change = (int) val != hdspm_input_select(hdspm);
+ hdspm_set_input_select(hdspm, val);
+ spin_unlock_irq(&hdspm->lock);
+ 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);
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.enumerated.item[0] = hdspm_ds_wire(hdspm);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+ spin_lock_irq(&hdspm->lock);
+ change = (int) val != hdspm_ds_wire(hdspm);
+ hdspm_set_ds_wire(hdspm, val);
+ spin_unlock_irq(&hdspm->lock);
+ 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);
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.enumerated.item[0] = hdspm_qs_wire(hdspm);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+ spin_lock_irq(&hdspm->lock);
+ change = val != hdspm_qs_wire(hdspm);
+ hdspm_set_qs_wire(hdspm, val);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.enumerated.item[0] = hdspm_tristate(hdspm, regmask);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+
+ spin_lock_irq(&hdspm->lock);
+ change = val != hdspm_tristate(hdspm, regmask);
+ hdspm_set_tristate(hdspm, val, regmask);
+ spin_unlock_irq(&hdspm->lock);
+ 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);
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.enumerated.item[0] = hdspm_madi_speedmode(hdspm);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+ spin_lock_irq(&hdspm->lock);
+ change = val != hdspm_madi_speedmode(hdspm);
+ hdspm_set_madi_speedmode(hdspm, val);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+
+ spin_lock_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);
+
+ spin_unlock_irq(&hdspm->lock);
+
+ 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];
+
+ spin_lock_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);
+ }
+ spin_unlock_irq(&hdspm->lock);
+
+ 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;
+
+ spin_lock_irq(&hdspm->lock);
+ ucontrol->value.integer.value[0] =
+ (hdspm_read_pb_gain(hdspm, channel, channel)*64)/UNITY_GAIN;
+ spin_unlock_irq(&hdspm->lock);
+
+ 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;
+
+ spin_lock_irq(&hdspm->lock);
+ change =
+ gain != hdspm_read_pb_gain(hdspm, channel,
+ channel);
+ if (change)
+ hdspm_write_pb_gain(hdspm, channel, channel,
+ gain);
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+ break;
+
+ case MADI:
+ status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+ if (status2 & HDSPM_wcLock) {
+ if (status2 & HDSPM_wcSync)
+ return 2;
+ else
+ return 1;
+ }
+ return 0;
+ break;
+
+ case RayDAT:
+ case AIO:
+ status = hdspm_read(hdspm, HDSPM_statusRegister);
+
+ if (status & 0x2000000)
+ return 2;
+ else if (status & 0x1000000)
+ return 1;
+ return 0;
+
+ break;
+
+ 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<<idx)) ? 1 : 0;
+ sync = (status & (0x100<<idx)) ? 1 : 0;
+
+ if (lock && sync)
+ return 2;
+ else if (lock)
+ return 1;
+ return 0;
+}
+
+
+static int hdspm_sync_in_sync_check(struct hdspm *hdspm)
+{
+ int status, lock = 0, sync = 0;
+
+ switch (hdspm->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", 0),
+ HDSPM_TCO_VIDEO_INPUT_FORMAT("Video Input Format", 0)
+};
+
+
+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",
+ (hdspm->control_register & HDSPM_LineOut) ? "on " : "off");
+
+ snd_iprintf(buffer,
+ "ClearTrackMarker = %s, Transmit in %s Channel Mode, "
+ "Auto Input %s\n",
+ (hdspm->control_register & HDSPM_clr_tms) ? "on" : "off",
+ (hdspm->control_register & HDSPM_TX_64ch) ? "64" : "56",
+ (hdspm->control_register & HDSPM_AutoInp) ? "on" : "off");
+
+
+ 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",
+ (hdspm->
+ control_register & HDSPM_clr_tms) ? "on" : "off",
+ (hdspm->
+ control_register & HDSPM_Emphasis) ? "on" : "off",
+ (hdspm->
+ control_register & HDSPM_Dolby) ? "on" : "off");
+
+
+ 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;
+
+ spin_lock_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) {
+ spin_unlock_irq(&hdspm->lock);
+ _snd_pcm_hw_param_setempty(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ return -EBUSY;
+ }
+
+ if (params_period_size(params) != hdspm->period_bytes / 4) {
+ spin_unlock_irq(&hdspm->lock);
+ _snd_pcm_hw_param_setempty(params,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+ return -EBUSY;
+ }
+
+ }
+ /* We're fine. */
+ spin_unlock_irq(&hdspm->lock);
+
+ /* how to make sure that the rate matches an externally-set one ? */
+
+ spin_lock_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);
+ spin_unlock_irq(&hdspm->lock);
+ _snd_pcm_hw_param_setempty(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ return err;
+ }
+ spin_unlock_irq(&hdspm->lock);
+
+ 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",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture",
+ snd_pcm_sgbuf_get_addr(substream, 0));
+ */
+ /*
+ dev_dbg(hdspm->card->dev,
+ "set_hwparams: %s %d Hz, %d channels, bs = %d\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture",
+ 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;
+
+ spin_lock(&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();
+ spin_unlock(&hdspm->lock);
+ 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;
+ spin_unlock(&hdspm->lock);
+
+ 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 const unsigned int hdspm_aes32_sample_rates[] = {
+ 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000
+};
+
+static const struct snd_pcm_hw_constraint_list
+hdspm_hw_constraints_aes32_sample_rates = {
+ .count = ARRAY_SIZE(hdspm_aes32_sample_rates),
+ .list = hdspm_aes32_sample_rates,
+ .mask = 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);
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&hdspm->lock);
+
+ 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_KNOT;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hdspm_hw_constraints_aes32_sample_rates);
+ } 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);
+
+ spin_lock_irq(&hdspm->lock);
+
+ if (playback) {
+ hdspm->playback_pid = -1;
+ hdspm->playback_substream = NULL;
+ } else {
+ hdspm->capture_pid = -1;
+ hdspm->capture_substream = NULL;
+ }
+
+ spin_unlock_irq(&hdspm->lock);
+
+ 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 inline int copy_u32_le(void __user *dest, void __iomem *src)
+{
+ u32 val = readl(src);
+ return copy_to_user(dest, &val, 4);
+}
+
+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, &ltc, 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));
+ spin_lock_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;
+ spin_unlock_irq(&hdspm->lock);
+ 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;
+
+ 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;
+ strlcpy(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;
+ strcpy(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;
+ strcpy(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);
+
+ strcpy(card->mixername, "Xilinx FPGA");
+ strcpy(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 = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ pci_set_master(hdspm->pci);
+
+ err = pci_request_regions(pci, "hdspm");
+ if (err < 0)
+ return err;
+
+ hdspm->port = pci_resource_start(pci, 0);
+ io_extent = pci_resource_len(pci, 0);
+
+ dev_dbg(card->dev, "grabbed memory region 0x%lx-0x%lx\n",
+ hdspm->port, hdspm->port + io_extent - 1);
+
+ hdspm->iobase = ioremap(hdspm->port, io_extent);
+ if (!hdspm->iobase) {
+ dev_err(card->dev, "unable to remap region 0x%lx-0x%lx\n",
+ hdspm->port, hdspm->port + io_extent - 1);
+ return -EBUSY;
+ }
+ 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 (request_irq(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 = kzalloc(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(sizeof(*hdspm->tco), GFP_KERNEL);
+ 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(sizeof(*hdspm->tco), GFP_KERNEL);
+ 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 int snd_hdspm_free(struct hdspm * hdspm)
+{
+
+ 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);
+ }
+
+ if (hdspm->irq >= 0)
+ free_irq(hdspm->irq, (void *) hdspm);
+
+ kfree(hdspm->mixer);
+ iounmap(hdspm->iobase);
+
+ if (hdspm->port)
+ pci_release_regions(hdspm->pci);
+
+ if (pci_is_enabled(hdspm->pci))
+ pci_disable_device(hdspm->pci);
+ return 0;
+}
+
+
+static void snd_hdspm_card_free(struct snd_card *card)
+{
+ struct hdspm *hdspm = card->private_data;
+
+ if (hdspm)
+ snd_hdspm_free(hdspm);
+}
+
+
+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_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 free_card;
+
+ 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 free_card;
+
+ pci_set_drvdata(pci, card);
+
+ dev++;
+ return 0;
+
+free_card:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_hdspm_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver hdspm_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_hdspm_ids,
+ .probe = snd_hdspm_probe,
+ .remove = snd_hdspm_remove,
+};
+
+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..459696844
--- /dev/null
+++ b/sound/pci/rme9652/rme9652.c
@@ -0,0 +1,2634 @@
+// 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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/nospec.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+#include <asm/current.h>
+
+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 <pbd@op.net>, Winfried Ritsch");
+MODULE_DESCRIPTION("RME Digi9652/Digi9636");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME,Hammerfall},"
+ "{RME,Hammerfall-Light}}");
+
+/* 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 */
+
+ 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 int snd_hammerfall_get_buffer(struct pci_dev *pci, struct snd_dma_buffer *dmab, size_t size)
+{
+ return snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, size, dmab);
+}
+
+static void snd_hammerfall_free_buffer(struct snd_dma_buffer *dmab, struct pci_dev *pci)
+{
+ if (dmab->area)
+ snd_dma_free_pages(dmab);
+}
+
+
+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)
+{
+ unsigned long flags;
+ int ret = 1;
+
+ spin_lock_irqsave(&rme9652->lock, flags);
+ if ((rme9652->playback_pid != rme9652->capture_pid) &&
+ (rme9652->playback_pid >= 0) && (rme9652->capture_pid >= 0)) {
+ ret = 0;
+ }
+ spin_unlock_irqrestore(&rme9652->lock, flags);
+ return ret;
+}
+
+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 <martin.kirst@freenet.de> 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;
+
+ spin_lock_irq(&s->lock);
+
+ if ((restart = s->running)) {
+ 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);
+
+ spin_unlock_irq(&s->lock);
+
+ 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.
+ */
+
+ spin_lock_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:
+ spin_unlock_irq(&rme9652->lock);
+ return -EINVAL;
+ }
+
+ if (reject_if_open && (rme9652->capture_pid >= 0 || rme9652->playback_pid >= 0)) {
+ spin_unlock_irq(&rme9652->lock);
+ return -EBUSY;
+ }
+
+ if ((restart = rme9652->running)) {
+ 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;
+ }
+ }
+
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ break;
+
+ case 0x6:
+ return 44100;
+ break;
+
+ case 0x5:
+ return 48000;
+ break;
+
+ case 0x4:
+ return 88200;
+ break;
+
+ case 0x3:
+ return 96000;
+ break;
+
+ case 0x0:
+ return 64000;
+ break;
+
+ default:
+ dev_err(s->card->dev,
+ "%s: unknown S/PDIF input rate (bits = 0x%x)\n",
+ s->card_name, rate_bits);
+ return 0;
+ break;
+ }
+}
+
+/*-----------------------------------------------------------------------------
+ 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);
+ spin_lock_irq(&rme9652->lock);
+ change = val != rme9652->creg_spdif;
+ rme9652->creg_spdif = val;
+ spin_unlock_irq(&rme9652->lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&rme9652->lock);
+ 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 ? */
+
+ if ((restart = rme9652->running)) {
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.enumerated.item[0] = rme9652_adat1_in(rme9652);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ spin_lock_irq(&rme9652->lock);
+ change = val != rme9652_adat1_in(rme9652);
+ if (change)
+ rme9652_set_adat1_input(rme9652, val);
+ spin_unlock_irq(&rme9652->lock);
+ 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);
+
+ if ((restart = rme9652->running)) {
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.enumerated.item[0] = rme9652_spdif_in(rme9652);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ spin_lock_irq(&rme9652->lock);
+ change = val != rme9652_spdif_in(rme9652);
+ if (change)
+ rme9652_set_spdif_input(rme9652, val);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ }
+
+ if ((restart = rme9652->running)) {
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.integer.value[0] = rme9652_spdif_out(rme9652);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ spin_lock_irq(&rme9652->lock);
+ change = (int)val != rme9652_spdif_out(rme9652);
+ rme9652_set_spdif_output(rme9652, val);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ }
+
+ if ((restart = rme9652->running)) {
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.enumerated.item[0] = rme9652_sync_mode(rme9652);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ spin_lock_irq(&rme9652->lock);
+ change = (int)val != rme9652_sync_mode(rme9652);
+ rme9652_set_sync_mode(rme9652, val);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ }
+
+ if ((restart = rme9652->running)) {
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.enumerated.item[0] = rme9652_sync_pref(rme9652);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ spin_lock_irq(&rme9652->lock);
+ change = (int)val != rme9652_sync_pref(rme9652);
+ rme9652_set_sync_pref(rme9652, val);
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ }
+
+ spin_lock_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<<chn));
+ }
+ }
+ spin_unlock_irq(&rme9652->lock);
+ return !!change;
+}
+
+#define RME9652_PASSTHRU(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_rme9652_info_passthru, \
+ .put = snd_rme9652_put_passthru, \
+ .get = snd_rme9652_get_passthru }
+
+#define snd_rme9652_info_passthru snd_ctl_boolean_mono_info
+
+static int snd_rme9652_get_passthru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.integer.value[0] = rme9652->passthru;
+ spin_unlock_irq(&rme9652->lock);
+ 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;
+ spin_lock_irq(&rme9652->lock);
+ change = (ucontrol->value.integer.value[0] != rme9652->passthru);
+ if (change)
+ err = rme9652_set_passthru(rme9652, val);
+ spin_unlock_irq(&rme9652->lock);
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+ ucontrol->value.integer.value[0] = rme9652_spdif_sample_rate(rme9652);
+ spin_unlock_irq(&rme9652->lock);
+ 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++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme9652_controls[idx], rme9652))) < 0)
+ return err;
+ if (idx == 1) /* IEC958 (S/PDIF) Stream */
+ rme9652->spdif_ctl = kctl;
+ }
+
+ if (rme9652->ss_channels == RME9652_NCHANNELS)
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme9652_adat3_check, rme9652))) < 0)
+ return err;
+
+ if (rme9652->hw_rev >= 15)
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme9652_adat1_input, rme9652))) < 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",
+ rme9652->passthru ? "yes" : "no");
+
+ 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",
+ (status & RME9652_tc_valid) ? "yes" : "no");
+
+ /* 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_free_buffers(struct snd_rme9652 *rme9652)
+{
+ snd_hammerfall_free_buffer(&rme9652->capture_dma_buf, rme9652->pci);
+ snd_hammerfall_free_buffer(&rme9652->playback_dma_buf, rme9652->pci);
+}
+
+static int snd_rme9652_free(struct snd_rme9652 *rme9652)
+{
+ if (rme9652->irq >= 0)
+ rme9652_stop(rme9652);
+ snd_rme9652_free_buffers(rme9652);
+
+ if (rme9652->irq >= 0)
+ free_irq(rme9652->irq, (void *)rme9652);
+ iounmap(rme9652->iobase);
+ if (rme9652->port)
+ pci_release_regions(rme9652->pci);
+
+ if (pci_is_enabled(rme9652->pci))
+ pci_disable_device(rme9652->pci);
+ return 0;
+}
+
+static int snd_rme9652_initialize_memory(struct snd_rme9652 *rme9652)
+{
+ unsigned long pb_bus, cb_bus;
+
+ if (snd_hammerfall_get_buffer(rme9652->pci, &rme9652->capture_dma_buf, RME9652_DMA_AREA_BYTES) < 0 ||
+ snd_hammerfall_get_buffer(rme9652->pci, &rme9652->playback_dma_buf, RME9652_DMA_AREA_BYTES) < 0) {
+ if (rme9652->capture_dma_buf.area)
+ snd_dma_free_pages(&rme9652->capture_dma_buf);
+ dev_err(rme9652->card->dev,
+ "%s: no buffers available\n", rme9652->card_name);
+ return -ENOMEM;
+ }
+
+ /* Align to bus-space 64K boundary */
+
+ cb_bus = ALIGN(rme9652->capture_dma_buf.addr, 0x10000ul);
+ pb_bus = ALIGN(rme9652->playback_dma_buf.addr, 0x10000ul);
+
+ /* Tell the card where it is */
+
+ rme9652_write(rme9652, RME9652_rec_buffer, cb_bus);
+ rme9652_write(rme9652, RME9652_play_buffer, pb_bus);
+
+ rme9652->capture_buffer = rme9652->capture_dma_buf.area + (cb_bus - rme9652->capture_dma_buf.addr);
+ rme9652->playback_buffer = rme9652->playback_dma_buf.area + (pb_bus - rme9652->playback_dma_buf.addr);
+
+ 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;
+
+ if ((mapped_channel = rme9652->channel_map[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,
+ void __user *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_user(channel_buf + pos, src, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_rme9652_playback_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *src, 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;
+ memcpy(channel_buf + pos, src, count);
+ return 0;
+}
+
+static int snd_rme9652_capture_copy(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void __user *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_user(dst, channel_buf + pos, count))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_rme9652_capture_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ void *dst, 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;
+ memcpy(dst, channel_buf + pos, count);
+ 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;
+
+ spin_lock_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)) {
+ spin_unlock_irq(&rme9652->lock);
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+ return -EBUSY;
+ }
+
+ if (params_period_size(params) != rme9652->period_bytes / 4) {
+ spin_unlock_irq(&rme9652->lock);
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+ return -EBUSY;
+ }
+
+ /* We're fine. */
+
+ spin_unlock_irq(&rme9652->lock);
+ return 0;
+
+ } else {
+ spin_unlock_irq(&rme9652->lock);
+ }
+
+ /* how to make sure that the rate matches an externally-set one ?
+ */
+
+ if ((err = rme9652_set_rate(rme9652, params_rate(params))) < 0) {
+ _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+ return err;
+ }
+
+ if ((err = rme9652_set_interrupt_interval(rme9652, params_period_size(params))) < 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;
+ spin_lock(&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();
+ spin_unlock(&rme9652->lock);
+ 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;
+ spin_unlock(&rme9652->lock);
+
+ return 0;
+}
+
+static int snd_rme9652_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+ unsigned long flags;
+
+ spin_lock_irqsave(&rme9652->lock, flags);
+ if (!rme9652->running)
+ rme9652_reset_hw_pointer(rme9652);
+ spin_unlock_irqrestore(&rme9652->lock, flags);
+ 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;
+
+ spin_lock_irq(&rme9652->lock);
+
+ snd_pcm_set_sync(substream);
+
+ runtime->hw = snd_rme9652_playback_subinfo;
+ runtime->dma_area = rme9652->playback_buffer;
+ runtime->dma_bytes = RME9652_DMA_AREA_BYTES;
+
+ if (rme9652->capture_substream == NULL) {
+ rme9652_stop(rme9652);
+ rme9652_set_thru(rme9652, -1, 0);
+ }
+
+ rme9652->playback_pid = current->pid;
+ rme9652->playback_substream = substream;
+
+ spin_unlock_irq(&rme9652->lock);
+
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+
+ rme9652->playback_pid = -1;
+ rme9652->playback_substream = NULL;
+
+ spin_unlock_irq(&rme9652->lock);
+
+ 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;
+
+ spin_lock_irq(&rme9652->lock);
+
+ snd_pcm_set_sync(substream);
+
+ runtime->hw = snd_rme9652_capture_subinfo;
+ runtime->dma_area = rme9652->capture_buffer;
+ runtime->dma_bytes = RME9652_DMA_AREA_BYTES;
+
+ if (rme9652->playback_substream == NULL) {
+ rme9652_stop(rme9652);
+ rme9652_set_thru(rme9652, -1, 0);
+ }
+
+ rme9652->capture_pid = current->pid;
+ rme9652->capture_substream = substream;
+
+ spin_unlock_irq(&rme9652->lock);
+
+ 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);
+
+ spin_lock_irq(&rme9652->lock);
+
+ rme9652->capture_pid = -1;
+ rme9652->capture_substream = NULL;
+
+ spin_unlock_irq(&rme9652->lock);
+ 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_user = snd_rme9652_playback_copy,
+ .copy_kernel = snd_rme9652_playback_copy_kernel,
+ .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_user = snd_rme9652_capture_copy,
+ .copy_kernel = snd_rme9652_capture_copy_kernel,
+};
+
+static int snd_rme9652_create_pcm(struct snd_card *card,
+ struct snd_rme9652 *rme9652)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ if ((err = snd_pcm_new(card,
+ rme9652->card_name,
+ 0, 1, 1, &pcm)) < 0) {
+ return err;
+ }
+
+ rme9652->pcm = pcm;
+ pcm->private_data = rme9652;
+ strcpy(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;
+ }
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ spin_lock_init(&rme9652->lock);
+
+ if ((err = pci_request_regions(pci, "rme9652")) < 0)
+ return err;
+ rme9652->port = pci_resource_start(pci, 0);
+ rme9652->iobase = ioremap(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 (request_irq(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 */
+ strcpy(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 */
+ strcpy(card->driver, "RME9636");
+ rme9652->card_name = "RME Digi9636 (Rev G)";
+ rme9652->ss_channels = RME9636_NCHANNELS;
+ break;
+ case 4: /* W52_G EPROM */
+ strcpy(card->driver, "RME9652");
+ rme9652->card_name = "RME Digi9652 (Rev G)";
+ rme9652->ss_channels = RME9652_NCHANNELS;
+ break;
+ case 3: /* original eprom */
+ strcpy(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);
+
+ if ((err = snd_rme9652_initialize_memory(rme9652)) < 0) {
+ return err;
+ }
+
+ if ((err = snd_rme9652_create_pcm(card, rme9652)) < 0) {
+ return err;
+ }
+
+ if ((err = snd_rme9652_create_controls(card, rme9652)) < 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 void snd_rme9652_card_free(struct snd_card *card)
+{
+ struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) card->private_data;
+
+ if (rme9652)
+ snd_rme9652_free(rme9652);
+}
+
+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_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 free_card;
+
+ strcpy(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) {
+free_card:
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_rme9652_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver rme9652_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_rme9652_ids,
+ .probe = snd_rme9652_probe,
+ .remove = snd_rme9652_remove,
+};
+
+module_pci_driver(rme9652_driver);
diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c
new file mode 100644
index 000000000..7bf6059d5
--- /dev/null
+++ b/sound/pci/sis7019.c
@@ -0,0 +1,1446 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for SiS7019 Audio Accelerator
+ *
+ * Copyright (C) 2004-2007, David Dillow
+ * Written by David Dillow <dave@thedillows.org>
+ * Inspired by the Trident 4D-WaveDX/NX driver.
+ *
+ * All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include "sis7019.h"
+
+MODULE_AUTHOR("David Dillow <dave@thedillows.org>");
+MODULE_DESCRIPTION("SiS7019");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{SiS,SiS7019 Audio Accelerator}}");
+
+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.
+ */
+#ifdef CONFIG_PM_SLEEP
+#define SIS_SUSPEND_PAGES 4
+#else
+#define SIS_SUSPEND_PAGES 1
+#endif
+
+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 = (((rate << 12) + 24000) / 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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sis->voice_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&sis->voice_lock, flags);
+}
+
+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)
+{
+ struct voice *voice;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sis->voice_lock, flags);
+ voice = __sis_alloc_playback_voice(sis);
+ spin_unlock_irqrestore(&sis->voice_lock, flags);
+
+ return voice;
+}
+
+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;
+ unsigned long flags;
+ 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) {
+ spin_lock_irqsave(&sis->voice_lock, flags);
+ voice->timing = __sis_alloc_playback_voice(sis);
+ if (voice->timing)
+ __sis_map_silence(sis);
+ spin_unlock_irqrestore(&sis->voice_lock, flags);
+ 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;
+ unsigned long flags;
+
+ /* FIXME: The driver only supports recording from one channel
+ * at the moment, but it could support more.
+ */
+ spin_lock_irqsave(&sis->voice_lock, flags);
+ if (voice->flags & VOICE_IN_USE)
+ voice = NULL;
+ else
+ voice->flags |= VOICE_IN_USE;
+ spin_unlock_irqrestore(&sis->voice_lock, flags);
+
+ 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;
+ strcpy(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...
+ */
+ mutex_lock(&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:
+ mutex_unlock(&sis->ac97_mutex);
+
+ 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_free_suspend(struct sis7019 *sis)
+{
+ int i;
+
+ for (i = 0; i < SIS_SUSPEND_PAGES; i++)
+ kfree(sis->suspend_state[i]);
+}
+
+static int sis_chip_free(struct sis7019 *sis)
+{
+ /* 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);
+
+ iounmap(sis->ioaddr);
+ pci_release_regions(sis->pci);
+ pci_disable_device(sis->pci);
+ sis_free_suspend(sis);
+ return 0;
+}
+
+static int sis_dev_free(struct snd_device *dev)
+{
+ struct sis7019 *sis = dev->device_data;
+ return sis_chip_free(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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(sis_pm, sis_suspend, sis_resume);
+#define SIS_PM_OPS &sis_pm
+#else
+#define SIS_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+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] = kmalloc(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;
+ static const struct snd_device_ops ops = {
+ .dev_free = sis_dev_free,
+ };
+ int rc;
+ int i;
+
+ rc = pci_enable_device(pci);
+ if (rc)
+ goto error_out;
+
+ 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");
+ goto error_out_enabled;
+ }
+
+ memset(sis, 0, sizeof(*sis));
+ 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 = pci_request_regions(pci, "SiS7019");
+ if (rc) {
+ dev_err(&pci->dev, "unable request regions\n");
+ goto error_out_enabled;
+ }
+
+ rc = -EIO;
+ sis->ioaddr = ioremap(pci_resource_start(pci, 1), 0x4000);
+ if (!sis->ioaddr) {
+ dev_err(&pci->dev, "unable to remap MMIO, aborting\n");
+ goto error_out_cleanup;
+ }
+
+ rc = sis_alloc_suspend(sis);
+ if (rc < 0) {
+ dev_err(&pci->dev, "unable to allocate state storage\n");
+ goto error_out_cleanup;
+ }
+
+ rc = sis_chip_init(sis);
+ if (rc)
+ goto error_out_cleanup;
+
+ 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);
+ goto error_out_cleanup;
+ }
+
+ 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);
+
+ rc = snd_device_new(card, SNDRV_DEV_LOWLEVEL, sis, &ops);
+ if (rc)
+ goto error_out_cleanup;
+
+ return 0;
+
+error_out_cleanup:
+ sis_chip_free(sis);
+
+error_out_enabled:
+ pci_disable_device(pci);
+
+error_out:
+ return rc;
+}
+
+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;
+
+ rc = -ENOENT;
+ if (!enable)
+ goto error_out;
+
+ /* 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_card_new(&pci->dev, index, id, THIS_MODULE,
+ sizeof(*sis), &card);
+ if (rc < 0)
+ goto error_out;
+
+ strcpy(card->driver, "SiS7019");
+ strcpy(card->shortname, "SiS7019");
+ rc = sis_chip_create(card, pci);
+ if (rc)
+ goto card_error_out;
+
+ sis = card->private_data;
+
+ rc = sis_mixer_create(sis);
+ if (rc)
+ goto card_error_out;
+
+ rc = sis_pcm_create(sis);
+ if (rc)
+ goto card_error_out;
+
+ 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)
+ goto card_error_out;
+
+ pci_set_drvdata(pci, card);
+ return 0;
+
+card_error_out:
+ snd_card_free(card);
+
+error_out:
+ return rc;
+}
+
+static void snd_sis7019_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver sis7019_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_sis7019_ids,
+ .probe = snd_sis7019_probe,
+ .remove = snd_sis7019_remove,
+ .driver = {
+ .pm = SIS_PM_OPS,
+ },
+};
+
+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 <dave@thedillows.org>
+ * 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..ecdd54d7a
--- /dev/null
+++ b/sound/pci/sonicvibes.c
@@ -0,0 +1,1516 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for S3 SonicVibes soundcard
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ * BUGS:
+ * It looks like 86c617 rev 3 doesn't supports DDMA buffers above 16MB?
+ * Driver sometimes hangs... Nobody knows why at this moment...
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("S3 SonicVibes PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{S3,SonicVibes PCI}}");
+
+#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), 0, },
+ { 0, }
+};
+
+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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sonic->reg_lock, flags);
+ outb(reg, SV_REG(sonic, INDEX));
+ udelay(10);
+ outb(value, SV_REG(sonic, DATA));
+ udelay(10);
+ spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+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 long flags;
+ unsigned char value;
+
+ spin_lock_irqsave(&sonic->reg_lock, flags);
+ outb(reg, SV_REG(sonic, INDEX));
+ udelay(10);
+ value = inb(SV_REG(sonic, DATA));
+ udelay(10);
+ spin_unlock_irqrestore(&sonic->reg_lock, flags);
+ 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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sonic->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+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 long flags;
+ unsigned int r, m, n;
+
+ snd_sonicvibes_pll(rate, &r, &m, &n);
+ if (sonic != NULL) {
+ spin_lock_irqsave(&sonic->reg_lock, flags);
+ snd_sonicvibes_out1(sonic, reg, m);
+ snd_sonicvibes_out1(sonic, reg + 1, r | n);
+ spin_unlock_irqrestore(&sonic->reg_lock, flags);
+ }
+}
+
+static void snd_sonicvibes_set_adc_rate(struct sonicvibes * sonic, unsigned int rate)
+{
+ unsigned long flags;
+ 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);
+ }
+ spin_lock_irqsave(&sonic->reg_lock, flags);
+ snd_sonicvibes_out1(sonic, SV_IREG_ADC_ALT_RATE, (div - 1) << 4);
+ snd_sonicvibes_out1(sonic, SV_IREG_ADC_CLOCK, clock);
+ spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+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;
+ unsigned long flags;
+
+ div = (rate * 65536 + SV_FULLRATE / 2) / SV_FULLRATE;
+ if (div > 65535)
+ div = 65535;
+ spin_lock_irqsave(&sonic->reg_lock, flags);
+ snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_HIGH, div >> 8);
+ snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_LOW, div);
+ spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+static int snd_sonicvibes_trigger(struct sonicvibes * sonic, int what, int cmd)
+{
+ int result = 0;
+
+ spin_lock(&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 {
+ result = -EINVAL;
+ }
+ spin_unlock(&sonic->reg_lock);
+ return result;
+}
+
+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;
+
+ spin_lock(&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);
+ spin_unlock(&sonic->reg_lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&sonic->reg_lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+
+ if ((err = snd_pcm_new(sonic->card, "s3_86c617", device, 1, 1, &pcm)) < 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;
+ strcpy(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);
+
+ spin_lock_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;
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+
+ spin_lock_irq(&sonic->reg_lock);
+ ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, reg)>> shift) & mask;
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+ spin_lock_irq(&sonic->reg_lock);
+ oval = snd_sonicvibes_in1(sonic, reg);
+ val = (oval & ~(mask << shift)) | val;
+ change = val != oval;
+ snd_sonicvibes_out1(sonic, reg, val);
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&sonic->reg_lock);
+ 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;
+ strcpy(card->mixername, "S3 SonicVibes");
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_controls); idx++) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_sonicvibes_controls[idx], sonic))) < 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",
+ sonic->srs_space & 0x80 ? "off" : "on");
+ 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", tmp & 0x01 ? "on" : "off");
+ snd_iprintf(buffer, "Ext. Rx to synth : %s\n", tmp & 0x02 ? "on" : "off");
+ snd_iprintf(buffer, "MIDI to ext. Tx : %s\n", tmp & 0x04 ? "on" : "off");
+}
+
+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 int snd_sonicvibes_free(struct sonicvibes *sonic)
+{
+ 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);
+ if (sonic->irq >= 0)
+ free_irq(sonic->irq, sonic);
+ release_and_free_resource(sonic->res_dmaa);
+ release_and_free_resource(sonic->res_dmac);
+ pci_release_regions(sonic->pci);
+ pci_disable_device(sonic->pci);
+ kfree(sonic);
+ return 0;
+}
+
+static int snd_sonicvibes_dev_free(struct snd_device *device)
+{
+ struct sonicvibes *sonic = device->device_data;
+ return snd_sonicvibes_free(sonic);
+}
+
+static int snd_sonicvibes_create(struct snd_card *card,
+ struct pci_dev *pci,
+ int reverb,
+ int mge,
+ struct sonicvibes **rsonic)
+{
+ struct sonicvibes *sonic;
+ unsigned int dmaa, dmac;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_sonicvibes_dev_free,
+ };
+
+ *rsonic = NULL;
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 24 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(24)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(24)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 24bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ sonic = kzalloc(sizeof(*sonic), GFP_KERNEL);
+ if (sonic == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ spin_lock_init(&sonic->reg_lock);
+ sonic->card = card;
+ sonic->pci = pci;
+ sonic->irq = -1;
+
+ if ((err = pci_request_regions(pci, "S3 SonicVibes")) < 0) {
+ kfree(sonic);
+ pci_disable_device(pci);
+ 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 (request_irq(pci->irq, snd_sonicvibes_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, sonic)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_sonicvibes_free(sonic);
+ return -EBUSY;
+ }
+ sonic->irq = pci->irq;
+ card->sync_irq = sonic->irq;
+
+ 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);
+
+ if ((sonic->res_dmaa = request_region(dmaa, 0x10, "S3 SonicVibes DDMA-A")) == NULL) {
+ snd_sonicvibes_free(sonic);
+ dev_err(card->dev,
+ "unable to grab DDMA-A port at 0x%x-0x%x\n",
+ dmaa, dmaa + 0x10 - 1);
+ return -EBUSY;
+ }
+ if ((sonic->res_dmac = request_region(dmac, 0x10, "S3 SonicVibes DDMA-C")) == NULL) {
+ snd_sonicvibes_free(sonic);
+ 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);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, sonic, &ops)) < 0) {
+ snd_sonicvibes_free(sonic);
+ return err;
+ }
+
+ snd_sonicvibes_proc_init(sonic);
+
+ *rsonic = 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++)
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_sonicvibes_midi_controls[idx], sonic))) < 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 idx, 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 < 0)
+ return err;
+ for (idx = 0; idx < 5; idx++) {
+ if (pci_resource_start(pci, idx) == 0 ||
+ !(pci_resource_flags(pci, idx) & IORESOURCE_IO)) {
+ snd_card_free(card);
+ return -ENODEV;
+ }
+ }
+ if ((err = snd_sonicvibes_create(card, pci,
+ reverb[dev] ? 1 : 0,
+ mge[dev] ? 1 : 0,
+ &sonic)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ strcpy(card->driver, "SonicVibes");
+ strcpy(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);
+
+ if ((err = snd_sonicvibes_pcm(sonic, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_sonicvibes_mixer(sonic)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SONICVIBES,
+ sonic->midi_port,
+ MPU401_INFO_INTEGRATED |
+ MPU401_INFO_IRQ_HOOK,
+ -1, &midi_uart)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ snd_sonicvibes_midi(sonic, midi_uart);
+ if ((err = snd_opl3_create(card, sonic->synth_port,
+ sonic->synth_port + 2,
+ OPL3_HW_OPL3_SV, 1, &opl3)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ err = snd_sonicvibes_create_gameport(sonic);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_sonic_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver sonicvibes_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_sonic_ids,
+ .probe = snd_sonic_probe,
+ .remove = snd_sonic_remove,
+};
+
+module_pci_driver(sonicvibes_driver);
diff --git a/sound/pci/trident/Makefile b/sound/pci/trident/Makefile
new file mode 100644
index 000000000..e8975bc37
--- /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 <perex@perex.cz>
+#
+
+snd-trident-objs := 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..5bc79da6e
--- /dev/null
+++ b/sound/pci/trident/trident.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Trident 4DWave DX/NX & SiS SI7018 Audio PCI soundcard
+ *
+ * Driver was originated by Trident <audio@tridentmicro.com>
+ * Fri Feb 19 15:55:28 MST 1999
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include "trident.h"
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, <audio@tridentmicro.com>");
+MODULE_DESCRIPTION("Trident 4D-WaveDX/NX & SiS SI7018");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Trident,4DWave DX},"
+ "{Trident,4DWave NX},"
+ "{SiS,SI7018 PCI Audio},"
+ "{Best Union,Miss Melody 4DWave PCI},"
+ "{HIS,4DWave PCI},"
+ "{Warpspeed,ONSpeed 4DWave PCI},"
+ "{Aztech Systems,PCI 64-Q3D},"
+ "{Addonics,SV 750},"
+ "{CHIC,True Sound 4Dwave},"
+ "{Shark,Predator4D-PCI},"
+ "{Jaton,SonicWave 4D},"
+ "{Hoontech,SoundTrack Digital 4DWave NX}}");
+
+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),
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_NX),
+ 0, 0, 0},
+ {PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7018), 0, 0, 0},
+ { 0, }
+};
+
+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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if ((err = snd_trident_create(card, pci,
+ pcm_channels[dev],
+ ((pci->vendor << 16) | pci->device) == TRIDENT_DEVICE_ID_SI7018 ? 1 : 2,
+ wavetable_size[dev],
+ &trident)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ card->private_data = trident;
+
+ 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";
+ }
+ strcpy(card->driver, str);
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ strcpy(card->shortname, "SiS ");
+ } else {
+ strcpy(card->shortname, "Trident ");
+ }
+ strcat(card->shortname, str);
+ sprintf(card->longname, "%s PCI Audio at 0x%lx, irq %d",
+ card->shortname, trident->port, trident->irq);
+
+ if ((err = snd_trident_pcm(trident, pcm_dev++)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ switch (trident->device) {
+ case TRIDENT_DEVICE_ID_DX:
+ case TRIDENT_DEVICE_ID_NX:
+ if ((err = snd_trident_foldback_pcm(trident, pcm_dev++)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ break;
+ }
+ if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ if ((err = snd_trident_spdif_pcm(trident, pcm_dev++)) < 0) {
+ snd_card_free(card);
+ 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)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_trident_create_gameport(trident);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_trident_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver trident_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_trident_ids,
+ .probe = snd_trident_probe,
+ .remove = snd_trident_remove,
+#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..c7567edbe
--- /dev/null
+++ b/sound/pci/trident/trident.h
@@ -0,0 +1,429 @@
+/* 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 <sound/pcm.h>
+#include <sound/mpu401.h>
+#include <sound/ac97_codec.h>
+#include <sound/util_mem.h>
+
+#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<<SNDRV_TRIDENT_PAGE_SHIFT)-1)
+#define SNDRV_TRIDENT_MAX_PAGES 4096
+
+/*
+ * Direct registers
+ */
+
+#define TRID_REG(trident, x) ((trident)->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 */
+ unsigned long * shadow_entries; /* shadow entries with virtual addresses */
+ 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,
+ struct snd_trident ** rtrident);
+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);
+int snd_trident_attach_synthesizer(struct snd_trident * trident);
+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);
+struct snd_util_memblk *snd_trident_synth_alloc(struct snd_trident *trident, unsigned int size);
+int snd_trident_synth_free(struct snd_trident *trident, struct snd_util_memblk *blk);
+int snd_trident_synth_copy_from_user(struct snd_trident *trident, struct snd_util_memblk *blk,
+ int offset, const char __user *data, int size);
+
+#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..6e5037616
--- /dev/null
+++ b/sound/pci/trident/trident_main.c
@@ -0,0 +1,3901 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Maintained by Jaroslav Kysela <perex@perex.cz>
+ * 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 <thomas@winischhofer.net>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/gameport.h>
+#include <linux/dma-mapping.h>
+#include <linux/export.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "trident.h"
+#include <sound/asoundef.h>
+
+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 int snd_trident_free(struct snd_trident *trident);
+
+/*
+ * 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;
+ unsigned long flags;
+ struct snd_trident *trident = ac97->private_data;
+
+ spin_lock_irqsave(&trident->reg_lock, flags);
+ 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;
+ }
+
+ spin_unlock_irqrestore(&trident->reg_lock, flags);
+ 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;
+ unsigned long flags;
+ struct snd_trident *trident = ac97->private_data;
+
+ data = ((unsigned long) wdata) << 16;
+
+ spin_lock_irqsave(&trident->reg_lock, flags);
+ 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) {
+ spin_unlock_irqrestore(&trident->reg_lock, flags);
+ return;
+ }
+ outl(data, TRID_REG(trident, address));
+ spin_unlock_irqrestore(&trident->reg_lock, flags);
+}
+
+/*---------------------------------------------------------------------------
+ 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 = (((rate << 12) + 24000) / 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];
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+
+ 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;
+
+ spin_lock_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 = (((unsigned int) 48000L << 12) + (runtime->rate/2)) / 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);
+
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+
+ spin_lock_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;
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+ 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 */
+ spin_lock_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;
+ spin_unlock_irq(&trident->reg_lock);
+
+ 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;
+
+ spin_lock_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));
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+
+ 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;
+ spin_lock(&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));
+ }
+ spin_unlock(&trident->reg_lock);
+ 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;
+
+ spin_lock(&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;
+ }
+
+ spin_unlock(&trident->reg_lock);
+
+ 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;
+ spin_lock_irq(&trident->reg_lock);
+ trident->spdif_pcm_bits = trident->spdif_bits;
+ spin_unlock_irq(&trident->reg_lock);
+
+ 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;
+
+ spin_lock_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));
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ 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 */
+ spin_lock_irq(&trident->reg_lock);
+ outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+
+ if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm)) < 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;
+ strcpy(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;
+ if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback)) < 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;
+ strcpy(foldback->name, "Trident 4DWave");
+ substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ strcpy(substream->name, "Front Mixer");
+ substream = substream->next;
+ strcpy(substream->name, "Reverb Mixer");
+ substream = substream->next;
+ strcpy(substream->name, "Chorus Mixer");
+ if (num_chan == 4) {
+ substream = substream->next;
+ strcpy(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;
+
+ if ((err = snd_pcm_new(trident->card, "trident_dx_nx IEC958", device, 1, 0, &spdif)) < 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;
+ strcpy(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;
+
+ spin_lock_irq(&trident->reg_lock);
+ val = trident->spdif_ctrl;
+ ucontrol->value.integer.value[0] = val == kcontrol->private_value;
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+ spin_lock_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));
+ }
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ 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);
+
+ spin_lock_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;
+ spin_unlock_irq(&trident->reg_lock);
+ 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);
+ spin_lock_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));
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ 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);
+
+ spin_lock_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;
+ spin_unlock_irq(&trident->reg_lock);
+ 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);
+ spin_lock_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));
+ }
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+
+ spin_lock_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;
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+
+ spin_lock_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));
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+
+ spin_lock_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));
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+ }
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->vol;
+ mix->vol = val;
+ if (mix->voice != NULL)
+ snd_trident_write_vol_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ 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;
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->pan;
+ mix->pan = val;
+ if (mix->voice != NULL)
+ snd_trident_write_pan_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ 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);
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->rvol;
+ mix->rvol = val;
+ if (mix->voice != NULL)
+ snd_trident_write_rvol_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ 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);
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->cvol;
+ mix->cvol = val;
+ if (mix->voice != NULL)
+ snd_trident_write_cvol_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ 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(sizeof(*uctl), GFP_KERNEL);
+ if (!uctl)
+ return -ENOMEM;
+
+ if ((err = snd_ac97_bus(trident->card, 0, &ops, NULL, &trident->ac97_bus)) < 0)
+ goto __out;
+
+ memset(&_ac97, 0, sizeof(_ac97));
+ _ac97.private_data = trident;
+ trident->ac97_detect = 1;
+
+ __again:
+ if ((err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97)) < 0) {
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ if ((err = snd_trident_sis_reset(trident)) < 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) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_wave_control, trident))) < 0)
+ goto __out;
+ kctl->put(kctl, uctl);
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_music_control, trident))) < 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;
+ }
+ if ((trident->ctl_vol = snd_ctl_new1(&snd_trident_pcm_vol_control, trident)) == NULL)
+ goto __nomem;
+ if ((err = snd_ctl_add(card, trident->ctl_vol)))
+ goto __out;
+
+ if ((trident->ctl_pan = snd_ctl_new1(&snd_trident_pcm_pan_control, trident)) == NULL)
+ goto __nomem;
+ if ((err = snd_ctl_add(card, trident->ctl_pan)))
+ goto __out;
+
+ if ((trident->ctl_rvol = snd_ctl_new1(&snd_trident_pcm_rvol_control, trident)) == NULL)
+ goto __nomem;
+ if ((err = snd_ctl_add(card, trident->ctl_rvol)))
+ goto __out;
+
+ if ((trident->ctl_cvol = snd_ctl_new1(&snd_trident_pcm_cvol_control, trident)) == NULL)
+ goto __nomem;
+ if ((err = snd_ctl_add(card, trident->ctl_cvol)))
+ goto __out;
+
+ if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_ac97_rear_control, trident))) < 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;
+ if ((err = snd_ctl_add(card, kctl)) < 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;
+ if ((err = snd_ctl_add(card, kctl)) < 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;
+ if ((err = snd_ctl_add(card, kctl)) < 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;
+ if ((err = snd_ctl_add(card, kctl)) < 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", trident->spdif_ctrl == 0x28 ? "on" : "off");
+ if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ snd_iprintf(buffer, "Rear Speakers : %s\n", trident->ac97_ctrl & 0x00000010 ? "on" : "off");
+ 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);
+}
+
+static int snd_trident_dev_free(struct snd_device *device)
+{
+ struct snd_trident *trident = device->device_data;
+ return snd_trident_free(trident);
+}
+
+/*---------------------------------------------------------------------------
+ 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 */
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &trident->pci->dev,
+ 2 * SNDRV_TRIDENT_MAX_PAGES * 4, &trident->tlb.buffer) < 0) {
+ 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 shadow TLB page table (virtual addresses) */
+ trident->tlb.shadow_entries =
+ vmalloc(array_size(SNDRV_TRIDENT_MAX_PAGES,
+ sizeof(unsigned long)));
+ if (!trident->tlb.shadow_entries)
+ return -ENOMEM;
+
+ /* allocate and setup silent page and initialise TLB entries */
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &trident->pci->dev,
+ SNDRV_TRIDENT_PAGE_SIZE, &trident->tlb.silent_page) < 0) {
+ 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));
+ trident->tlb.shadow_entries[i] = (unsigned long)trident->tlb.silent_page.area;
+ }
+
+ /* 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;
+
+ if ((err = snd_trident_sis_reset(trident)) < 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 ** rtrident)
+{
+ struct snd_trident *trident;
+ int i, err;
+ struct snd_trident_voice *voice;
+ struct snd_trident_pcm_mixer *tmix;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_trident_dev_free,
+ };
+
+ *rtrident = NULL;
+
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 30 bits */
+ if (dma_set_mask(&pci->dev, DMA_BIT_MASK(30)) < 0 ||
+ dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(30)) < 0) {
+ dev_err(card->dev,
+ "architecture does not support 30bit PCI busmaster DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ trident = kzalloc(sizeof(*trident), GFP_KERNEL);
+ if (trident == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ 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;
+
+ trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
+ pci_set_master(pci);
+
+ if ((err = pci_request_regions(pci, "Trident Audio")) < 0) {
+ kfree(trident);
+ pci_disable_device(pci);
+ return err;
+ }
+ trident->port = pci_resource_start(pci, 0);
+
+ if (request_irq(pci->irq, snd_trident_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, trident)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_trident_free(trident);
+ return -EBUSY;
+ }
+ trident->irq = pci->irq;
+ card->sync_irq = trident->irq;
+
+ /* allocate 16k-aligned TLB for NX cards */
+ trident->tlb.entries = NULL;
+ trident->tlb.buffer.area = NULL;
+ if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ if ((err = snd_trident_tlb_alloc(trident)) < 0) {
+ snd_trident_free(trident);
+ 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) {
+ snd_trident_free(trident);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, trident, &ops)) < 0) {
+ snd_trident_free(trident);
+ return err;
+ }
+
+ if ((err = snd_trident_mixer(trident, pcm_spdif_device)) < 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);
+ *rtrident = trident;
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_free
+
+ Description: This routine will free the device specific class for
+ the 4DWave card.
+
+ Parameters: trident - device specific private data for 4DWave card
+
+ Returns: None.
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_free(struct snd_trident *trident)
+{
+ 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->irq >= 0)
+ free_irq(trident->irq, trident);
+ if (trident->tlb.buffer.area) {
+ outl(0, TRID_REG(trident, NX_TLBC));
+ snd_util_memhdr_free(trident->tlb.memhdr);
+ if (trident->tlb.silent_page.area)
+ snd_dma_free_pages(&trident->tlb.silent_page);
+ vfree(trident->tlb.shadow_entries);
+ snd_dma_free_pages(&trident->tlb.buffer);
+ }
+ pci_release_regions(trident->pci);
+ pci_disable_device(trident->pci);
+ kfree(trident);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ 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
+ spin_lock(&trident->reg_lock);
+ stimer = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+ chn_int = inl(TRID_REG(trident, T4D_AINT_A));
+ if (chn_int == 0)
+ goto __skip1;
+ outl(chn_int, TRID_REG(trident, T4D_AINT_A)); /* ack */
+ __skip1:
+ chn_int = inl(TRID_REG(trident, T4D_AINT_B));
+ if (chn_int == 0)
+ goto __skip2;
+ 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 */
+ __skip2:
+ spin_unlock(&trident->reg_lock);
+ }
+ 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;
+ unsigned long flags;
+ int idx;
+
+ spin_lock_irqsave(&trident->voice_alloc, flags);
+ if (type == SNDRV_TRIDENT_VOICE_TYPE_PCM) {
+ idx = snd_trident_allocate_pcm_channel(trident);
+ if(idx < 0) {
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ 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;
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return pvoice;
+ }
+ if (type == SNDRV_TRIDENT_VOICE_TYPE_SYNTH) {
+ idx = snd_trident_allocate_synth_channel(trident);
+ if(idx < 0) {
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return NULL;
+ }
+ pvoice = &trident->synth.voices[idx];
+ pvoice->use = 1;
+ pvoice->synth = 1;
+ pvoice->client = client;
+ pvoice->port = port;
+ pvoice->memblk = NULL;
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return pvoice;
+ }
+ if (type == SNDRV_TRIDENT_VOICE_TYPE_MIDI) {
+ }
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return NULL;
+}
+
+EXPORT_SYMBOL(snd_trident_alloc_voice);
+
+void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice)
+{
+ unsigned long flags;
+ void (*private_free)(struct snd_trident_voice *);
+
+ if (voice == NULL || !voice->use)
+ return;
+ snd_trident_clear_voices(trident, voice->number, voice->number);
+ spin_lock_irqsave(&trident->voice_alloc, flags);
+ 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;
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ 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..bb24dbf05
--- /dev/null
+++ b/sound/pci/trident/trident_memory.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * Copyright (c) by Scott McNab <sdm@fractalgraphics.com.au>
+ *
+ * Trident 4DWave-NX memory page allocation (TLB area)
+ * Trident chip can handle only 16MByte of the memory at the same time.
+ */
+
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#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,ptr,addr) \
+ do { (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1)); \
+ (trident)->tlb.shadow_entries[page] = (ptr); } while (0)
+#define __tlb_to_ptr(trident,page) \
+ (void*)((trident)->tlb.shadow_entries[page])
+#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,ptr,addr) __set_tlb_bus(trident,page,ptr,addr)
+/* fill TLB entrie(s) corresponding to page with silence pointer */
+#define set_silent_tlb(trident,page) __set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, 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 buffer address from aligned page */
+#define page_to_ptr(trident,page) __tlb_to_ptr(trident, page)
+/* 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_ptr(trident,page) __tlb_to_ptr(trident, (page) << 1)
+#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,
+ unsigned long ptr, dma_addr_t addr)
+{
+ page <<= 1;
+ __set_tlb_bus(trident, page, ptr, addr);
+ __set_tlb_bus(trident, page+1, ptr + SNDRV_TRIDENT_PAGE_SIZE, addr + SNDRV_TRIDENT_PAGE_SIZE);
+}
+static inline void set_silent_tlb(struct snd_trident *trident, int page)
+{
+ page <<= 1;
+ __set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+ __set_tlb_bus(trident, page+1, (unsigned long)trident->tlb.silent_page.area, 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_ptr(trident,page) __tlb_to_ptr(trident, (page) * UNIT_PAGES)
+#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,
+ unsigned long ptr, dma_addr_t addr)
+{
+ int i;
+ page *= UNIT_PAGES;
+ for (i = 0; i < UNIT_PAGES; i++, page++) {
+ __set_tlb_bus(trident, page, ptr, addr);
+ ptr += SNDRV_TRIDENT_PAGE_SIZE;
+ 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, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+}
+
+#endif /* PAGE_SIZE */
+
+/* calculate buffer pointer from offset address */
+static inline void *offset_ptr(struct snd_trident *trident, int offset)
+{
+ char *ptr;
+ ptr = page_to_ptr(trident, get_aligned_page(offset));
+ ptr += offset % ALIGN_PAGE_SIZE;
+ return (void*)ptr;
+}
+
+/* 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(unsigned long ptr)
+{
+ if (ptr & ~0x3fffffffUL) {
+ snd_printk(KERN_ERR "max memory size is 1GB!!\n");
+ return 0;
+ }
+ if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) {
+ snd_printk(KERN_ERR "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;
+
+
+
+ mutex_lock(&hdr->block_mutex);
+ blk = search_empty(hdr, runtime->dma_bytes);
+ if (blk == NULL) {
+ mutex_unlock(&hdr->block_mutex);
+ 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);
+ unsigned long ptr = (unsigned long)
+ snd_pcm_sgbuf_get_ptr(substream, ofs);
+ if (! is_valid_page(addr)) {
+ __snd_util_mem_free(hdr, blk);
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ set_tlb_bus(trident, page, ptr, addr);
+ }
+ mutex_unlock(&hdr->block_mutex);
+ 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;
+ unsigned long ptr;
+
+ 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;
+
+ mutex_lock(&hdr->block_mutex);
+ blk = search_empty(hdr, runtime->dma_bytes);
+ if (blk == NULL) {
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+
+ /* set TLB entries */
+ addr = runtime->dma_addr;
+ ptr = (unsigned long)runtime->dma_area;
+ for (page = firstpg(blk); page <= lastpg(blk); page++,
+ ptr += SNDRV_TRIDENT_PAGE_SIZE, addr += SNDRV_TRIDENT_PAGE_SIZE) {
+ if (! is_valid_page(addr)) {
+ __snd_util_mem_free(hdr, blk);
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ set_tlb_bus(trident, page, ptr, addr);
+ }
+ mutex_unlock(&hdr->block_mutex);
+ 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;
+ mutex_lock(&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);
+ mutex_unlock(&hdr->block_mutex);
+ return 0;
+}
diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c
new file mode 100644
index 000000000..154d88ce8
--- /dev/null
+++ b/sound/pci/via82xx.c
@@ -0,0 +1,2614 @@
+// 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 <perex@perex.cz>
+ * Tjeerd.Mulder <Tjeerd.Mulder@fujitsu-siemens.com>
+ * 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+/*
+ * Changes:
+ *
+ * Dec. 19, 2002 Takashi Iwai <tiwai@suse.de>
+ * - 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 <annabellesgarden@yahoo.de>
+ * - Optimize position calculation for the 823x chips.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+#if 0
+#define POINTER_DEBUG
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("VIA VT82xx audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{VIA,VT82C686A/B/C,pci},{VIA,VT8233A/C,8235}}");
+
+#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;
+#ifdef CONFIG_PM_SLEEP
+ 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;
+#endif
+
+ 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), TYPE_CARD_VIA686, }, /* 686A */
+ /* 0x1106, 0x3059 */
+ { PCI_VDEVICE(VIA, PCI_DEVICE_ID_VIA_8233_5), TYPE_CARD_VIA8233, }, /* VT8233 */
+ { 0, }
+};
+
+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_array(VIA_TABLE_SIZE,
+ sizeof(*dev->idx_table),
+ GFP_KERNEL);
+ 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);
+ if (!((val = snd_via82xx_codec_xread(chip)) & 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 */
+ spin_lock(&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 */
+ }
+ spin_unlock(&chip->reg_lock);
+ 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 */
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ spin_lock(&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 */
+ spin_unlock(&chip->reg_lock);
+
+ 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;
+
+ spin_lock(&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;
+ spin_unlock(&chip->reg_lock);
+
+ 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;
+
+ spin_lock_irq(&rec->lock);
+ if (rec->rate != rate) {
+ if (rec->rate && rec->used > 1) /* already set */
+ changed = -EINVAL;
+ else {
+ rec->rate = rate;
+ changed = 1;
+ }
+ }
+ spin_unlock_irq(&rec->lock);
+ 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;
+
+ if ((rate_changed = via_lock_rate(&chip->rates[0], ac97_rate)) < 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];
+ spin_lock_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;
+ }
+ spin_unlock_irq(&ratep->lock);
+
+ /* we may remove following constaint when we modify table entries
+ in interrupt */
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 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;
+
+ if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 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];
+ if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 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,
+ };
+
+ if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 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];
+ spin_lock_irq(&ratep->lock);
+ ratep->used--;
+ if (! ratep->used)
+ ratep->rate = 0;
+ spin_unlock_irq(&ratep->lock);
+ 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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 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;
+ if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 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;
+ struct resource *r;
+
+ if (!joystick)
+ return -ENODEV;
+
+ r = request_region(JOYSTICK_ADDR, 8, "VIA686 gameport");
+ if (!r) {
+ 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");
+ release_and_free_resource(r);
+ 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;
+ gameport_set_port_data(gp, r);
+
+ /* 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) {
+ struct resource *r = gameport_get_port_data(chip->gameport);
+
+ gameport_unregister_port(chip->gameport);
+ chip->gameport = NULL;
+ release_and_free_resource(r);
+ }
+}
+#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
+ */
+ struct snd_ctl_elem_id sid;
+ memset(&sid, 0, sizeof(sid));
+ strcpy(sid.name, "PCM Playback Volume");
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ if (! snd_ctl_find_id(chip->card, &sid)) {
+ 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);
+#ifdef CONFIG_PM_SLEEP
+ chip->mpu_port_saved = mpu_port;
+#endif
+ } 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 = request_region(mpu_port, 2, "VIA82xx MPU401"))
+ != NULL) {
+ 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);
+
+#ifdef CONFIG_PM_SLEEP
+ chip->legacy_saved = legacy;
+ chip->legacy_cfg_saved = legacy_cfg;
+#endif
+
+ 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));
+
+ if ((val = snd_via82xx_codec_xread(chip)) & 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 {
+ if ((val = snd_via82xx_codec_xread(chip)) & 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(snd_via82xx_pm, snd_via82xx_suspend, snd_via82xx_resume);
+#define SND_VIA82XX_PM_OPS &snd_via82xx_pm
+#else
+#define SND_VIA82XX_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static int snd_via82xx_free(struct via82xx *chip)
+{
+ unsigned int i;
+
+ if (chip->irq < 0)
+ goto __end_hw;
+ /* disable interrupts */
+ for (i = 0; i < chip->num_devs; i++)
+ snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ __end_hw:
+ release_and_free_resource(chip->mpu_res);
+ pci_release_regions(chip->pci);
+
+ 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);
+ }
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_via82xx_dev_free(struct snd_device *device)
+{
+ struct via82xx *chip = device->device_data;
+ return snd_via82xx_free(chip);
+}
+
+static int snd_via82xx_create(struct snd_card *card,
+ struct pci_dev *pci,
+ int chip_type,
+ int revision,
+ unsigned int ac97_clock,
+ struct via82xx **r_via)
+{
+ struct via82xx *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_via82xx_dev_free,
+ };
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ 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));
+
+ if ((err = pci_request_regions(pci, card->driver)) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->port = pci_resource_start(pci, 0);
+ if (request_irq(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);
+ snd_via82xx_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ if (ac97_clock >= 8000 && ac97_clock <= 48000)
+ chip->ac97_clock = ac97_clock;
+
+ if ((err = snd_via82xx_chip_init(chip)) < 0) {
+ snd_via82xx_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_via82xx_free(chip);
+ 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);
+
+ *r_via = chip;
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ card_type = pci_id->driver_data;
+ switch (card_type) {
+ case TYPE_CARD_VIA686:
+ strcpy(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;
+ strcpy(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)
+ strcpy(card->driver, "VIA8233A");
+ else if (pci->revision >= VIA_REV_8237)
+ strcpy(card->driver, "VIA8237"); /* no slog assignment */
+ else
+ strcpy(card->driver, "VIA8233");
+ break;
+ default:
+ dev_err(card->dev, "invalid card type %d\n", card_type);
+ err = -EINVAL;
+ goto __error;
+ }
+
+ if ((err = snd_via82xx_create(card, pci, chip_type, pci->revision,
+ ac97_clock, &chip)) < 0)
+ goto __error;
+ card->private_data = chip;
+ if ((err = snd_via82xx_mixer_new(chip, ac97_quirk)) < 0)
+ goto __error;
+
+ if (chip_type == TYPE_VIA686) {
+ if ((err = snd_via686_pcm_new(chip)) < 0 ||
+ (err = snd_via686_init_misc(chip)) < 0)
+ goto __error;
+ } else {
+ if (chip_type == TYPE_VIA8233A) {
+ if ((err = snd_via8233a_pcm_new(chip)) < 0)
+ goto __error;
+ // chip->dxs_fixed = 1; /* FIXME: use 48k for DXS #3? */
+ } else {
+ if ((err = snd_via8233_pcm_new(chip)) < 0)
+ goto __error;
+ 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;
+ }
+ }
+ if ((err = snd_via8233_init_misc(chip)) < 0)
+ goto __error;
+ }
+
+ /* 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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ return 0;
+
+ __error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_via82xx_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver via82xx_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_via82xx_ids,
+ .probe = snd_via82xx_probe,
+ .remove = snd_via82xx_remove,
+ .driver = {
+ .pm = SND_VIA82XX_PM_OPS,
+ },
+};
+
+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..addfa196d
--- /dev/null
+++ b/sound/pci/via82xx_modem.c
@@ -0,0 +1,1204 @@
+// 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 <perex@perex.cz>
+ * Tjeerd.Mulder <Tjeerd.Mulder@fujitsu-siemens.com>
+ * 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+/*
+ * Changes:
+ *
+ * Sep. 2, 2004 Sasha Khapyorsky <sashak@alsa-project.org>
+ * Modified from original audio driver 'via82xx.c' to support AC97
+ * modems.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#if 0
+#define POINTER_DEBUG
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("VIA VT82xx modem");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{VIA,VT82C686A/B/C modem,pci}}");
+
+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), TYPE_CARD_VIA82XX_MODEM, },
+ { 0, }
+};
+
+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_array(VIA_TABLE_SIZE,
+ sizeof(*dev->idx_table),
+ GFP_KERNEL);
+ 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);
+ if (!((val = snd_via82xx_codec_xread(chip)) & 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 */
+ spin_lock(&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 */
+ }
+ spin_unlock(&chip->reg_lock);
+ 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 && size < count) {
+ dev_dbg(chip->card->dev,
+ "invalid via82xx_cur_ptr, using last valid pointer\n");
+ res = viadev->lastpos;
+ } else {
+ 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;
+
+ spin_lock(&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);
+ spin_unlock(&chip->reg_lock);
+
+ 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;
+
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_rates)) < 0)
+ return err;
+
+ /* we may remove following constaint when we modify table entries
+ in interrupt */
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 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;
+ strcpy(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,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 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;
+
+ if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 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));
+
+ if ((val = snd_via82xx_codec_xread(chip)) & 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 {
+ if ((val = snd_via82xx_codec_xread(chip)) & 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * 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 SIMPLE_DEV_PM_OPS(snd_via82xx_pm, snd_via82xx_suspend, snd_via82xx_resume);
+#define SND_VIA82XX_PM_OPS &snd_via82xx_pm
+#else
+#define SND_VIA82XX_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static int snd_via82xx_free(struct via82xx_modem *chip)
+{
+ unsigned int i;
+
+ if (chip->irq < 0)
+ goto __end_hw;
+ /* disable interrupts */
+ for (i = 0; i < chip->num_devs; i++)
+ snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+ __end_hw:
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_via82xx_dev_free(struct snd_device *device)
+{
+ struct via82xx_modem *chip = device->device_data;
+ return snd_via82xx_free(chip);
+}
+
+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 **r_via)
+{
+ struct via82xx_modem *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_via82xx_dev_free,
+ };
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&chip->reg_lock);
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ if ((err = pci_request_regions(pci, card->driver)) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+ chip->port = pci_resource_start(pci, 0);
+ if (request_irq(pci->irq, snd_via82xx_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ snd_via82xx_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+ if (ac97_clock >= 8000 && ac97_clock <= 48000)
+ chip->ac97_clock = ac97_clock;
+
+ if ((err = snd_via82xx_chip_init(chip)) < 0) {
+ snd_via82xx_free(chip);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_via82xx_free(chip);
+ 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);
+
+ *r_via = chip;
+ 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_card_new(&pci->dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ card_type = pci_id->driver_data;
+ switch (card_type) {
+ case TYPE_CARD_VIA82XX_MODEM:
+ strcpy(card->driver, "VIA82XX-MODEM");
+ sprintf(card->shortname, "VIA 82XX modem");
+ break;
+ default:
+ dev_err(card->dev, "invalid card type %d\n", card_type);
+ err = -EINVAL;
+ goto __error;
+ }
+
+ if ((err = snd_via82xx_create(card, pci, chip_type, pci->revision,
+ ac97_clock, &chip)) < 0)
+ goto __error;
+ card->private_data = chip;
+ if ((err = snd_via82xx_mixer_new(chip)) < 0)
+ goto __error;
+
+ if ((err = snd_via686_pcm_new(chip)) < 0 )
+ goto __error;
+
+ /* 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);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ return 0;
+
+ __error:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_via82xx_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver via82xx_modem_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_via82xx_modem_ids,
+ .probe = snd_via82xx_probe,
+ .remove = snd_via82xx_remove,
+ .driver = {
+ .pm = SND_VIA82XX_PM_OPS,
+ },
+};
+
+module_pci_driver(via82xx_modem_driver);
diff --git a/sound/pci/vx222/Makefile b/sound/pci/vx222/Makefile
new file mode 100644
index 000000000..dda900e45
--- /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 <perex@perex.cz>
+#
+
+snd-vx222-objs := 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..f7800ed1b
--- /dev/null
+++ b/sound/pci/vx222/vx222.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Digigram VX222 V2/Mic PCI soundcards
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include "vx222.h"
+
+#define CARD_NAME "VX222"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Digigram VX222 V2/Mic");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram," CARD_NAME "}}");
+
+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[] = {
+ { 0x10b5, 0x9050, 0x1369, PCI_ANY_ID, 0, 0, VX_PCI_VX222_OLD, }, /* PLX */
+ { 0x10b5, 0x9030, 0x1369, PCI_ANY_ID, 0, 0, VX_PCI_VX222_NEW, }, /* PLX */
+ { 0, }
+};
+
+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_free(struct vx_core *chip)
+{
+ struct snd_vx222 *vx = to_vx222(chip);
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void*)chip);
+ if (vx->port[0])
+ pci_release_regions(vx->pci);
+ pci_disable_device(vx->pci);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_vx222_dev_free(struct snd_device *device)
+{
+ struct vx_core *chip = device->device_data;
+ return snd_vx222_free(chip);
+}
+
+
+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;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_vx222_dev_free,
+ };
+ const struct snd_vx_ops *vx_ops;
+
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 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) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ vx = to_vx222(chip);
+ vx->pci = pci;
+
+ if ((err = pci_request_regions(pci, CARD_NAME)) < 0) {
+ snd_vx222_free(chip);
+ return err;
+ }
+ for (i = 0; i < 2; i++)
+ vx->port[i] = pci_resource_start(pci, i + 1);
+
+ if (request_threaded_irq(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);
+ snd_vx222_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+ snd_vx222_free(chip);
+ return err;
+ }
+
+ *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_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;
+ }
+ if ((err = snd_vx222_create(card, pci, hw, &vx)) < 0) {
+ snd_card_free(card);
+ 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
+
+ if ((err = snd_vx_setup_firmware(&vx->core)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void snd_vx222_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 SIMPLE_DEV_PM_OPS(snd_vx222_pm, snd_vx222_suspend, snd_vx222_resume);
+#define SND_VX222_PM_OPS &snd_vx222_pm
+#else
+#define SND_VX222_PM_OPS NULL
+#endif
+
+static struct pci_driver vx222_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_vx222_ids,
+ .probe = snd_vx222_probe,
+ .remove = snd_vx222_remove,
+ .driver = {
+ .pm = SND_VX222_PM_OPS,
+ },
+};
+
+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 <tiwai@suse.de>
+ */
+
+#ifndef __VX222_H
+#define __VX222_H
+
+#include <sound/vx_core.h>
+
+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..23d4338dc
--- /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 <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#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];
+}
+
+/**
+ * snd_vx_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));
+}
+
+/**
+ * snd_vx_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));
+ */
+}
+
+/**
+ * snd_vx_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));
+}
+
+/**
+ * snd_vx_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;
+}
+
+
+/**
+ * vx_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 */
+ if ((err = vx2_load_xilinx_binary(vx, dsp)) < 0)
+ return err;
+ if ((err = vx2_test_xilinx(vx)) < 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);
+ mutex_lock(&_chip->mixer_mutex);
+ ucontrol->value.integer.value[0] = chip->input_level[0];
+ ucontrol->value.integer.value[1] = chip->input_level[1];
+ mutex_unlock(&_chip->mixer_mutex);
+ 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;
+ mutex_lock(&_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);
+ mutex_unlock(&_chip->mixer_mutex);
+ return 1;
+ }
+ mutex_unlock(&_chip->mixer_mutex);
+ 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;
+ mutex_lock(&_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);
+ mutex_unlock(&_chip->mixer_mutex);
+ return 1;
+ }
+ mutex_unlock(&_chip->mixer_mutex);
+ 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 */
+ if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_input_level, chip))) < 0)
+ return err;
+ if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_level, chip))) < 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..40a1d83e1
--- /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 <perex@perex.cz>
+#
+
+snd-ymfpci-objs := 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..27fd10b97
--- /dev/null
+++ b/sound/pci/ymfpci/ymfpci.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The driver for the Yamaha's DS1/DS1E cards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include "ymfpci.h"
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Yamaha DS-1 PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Yamaha,YMF724},"
+ "{Yamaha,YMF724F},"
+ "{Yamaha,YMF740},"
+ "{Yamaha,YMF740C},"
+ "{Yamaha,YMF744},"
+ "{Yamaha,YMF754}}");
+
+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), 0, }, /* YMF724 */
+ { PCI_VDEVICE(YAMAHA, 0x000d), 0, }, /* YMF724F */
+ { PCI_VDEVICE(YAMAHA, 0x000a), 0, }, /* YMF740 */
+ { PCI_VDEVICE(YAMAHA, 0x000c), 0, }, /* YMF740C */
+ { PCI_VDEVICE(YAMAHA, 0x0010), 0, }, /* YMF744 */
+ { PCI_VDEVICE(YAMAHA, 0x0012), 0, }, /* YMF754 */
+ { 0, }
+};
+
+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:
+ dev_err(chip->card->dev,
+ "invalid joystick port %#x", io_port);
+ return -EINVAL;
+ }
+ }
+
+ if (!r) {
+ r = request_region(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");
+ release_and_free_resource(r);
+ 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;
+ gameport_set_port_data(gp, r);
+
+ 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) {
+ struct resource *r = gameport_get_port_data(chip->gameport);
+
+ gameport_unregister_port(chip->gameport);
+ chip->gameport = NULL;
+
+ release_and_free_resource(r);
+ }
+}
+#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_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ 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;
+ }
+
+ 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 = request_region(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 = request_region(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: fm_port[dev] = 0; break;
+ }
+ if (fm_port[dev] > 0)
+ fm_res = request_region(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: mpu_port[dev] = 0; break;
+ }
+ if (mpu_port[dev] > 0)
+ mpu_res = request_region(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, &chip);
+ if (err < 0) {
+ release_and_free_resource(mpu_res);
+ release_and_free_resource(fm_res);
+ goto free_card;
+ }
+ chip->fm_res = fm_res;
+ chip->mpu_res = mpu_res;
+ card->private_data = chip;
+
+ strcpy(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);
+ err = snd_ymfpci_pcm(chip, 0);
+ if (err < 0)
+ goto free_card;
+
+ err = snd_ymfpci_pcm_spdif(chip, 1);
+ if (err < 0)
+ goto free_card;
+
+ err = snd_ymfpci_mixer(chip, rear_switch[dev]);
+ if (err < 0)
+ goto free_card;
+
+ if (chip->ac97->ext_id & AC97_EI_SDAC) {
+ err = snd_ymfpci_pcm_4ch(chip, 2);
+ if (err < 0)
+ goto free_card;
+
+ err = snd_ymfpci_pcm2(chip, 3);
+ if (err < 0)
+ goto free_card;
+ }
+ err = snd_ymfpci_timer(chip, 0);
+ if (err < 0)
+ goto free_card;
+
+ if (chip->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 (chip->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");
+ goto free_card;
+ }
+ }
+ }
+
+ snd_ymfpci_create_gameport(chip, dev, legacy_ctrl, legacy_ctrl2);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto free_card;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+
+free_card:
+ snd_card_free(card);
+ return err;
+}
+
+static void snd_card_ymfpci_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver ymfpci_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_ymfpci_ids,
+ .probe = snd_card_ymfpci_probe,
+ .remove = snd_card_ymfpci_remove,
+#ifdef CONFIG_PM_SLEEP
+ .driver = {
+ .pm = &snd_ymfpci_pm,
+ },
+#endif
+};
+
+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..c73d8a5f4
--- /dev/null
+++ b/sound/pci/ymfpci/ymfpci.h
@@ -0,0 +1,374 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_YMFPCI_H
+#define __SOUND_YMFPCI_H
+
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Definitions for Yahama YMF724/740/744/754 chips
+ */
+
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#include <sound/timer.h>
+#include <linux/gameport.h>
+
+/*
+ * 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;
+};
+
+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;
+ struct resource *res_reg_area;
+ struct resource *fm_res;
+ struct resource *mpu_res;
+
+ unsigned short 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;
+
+#ifdef CONFIG_PM_SLEEP
+ u32 *saved_regs;
+ u32 saved_ydsxgr_mode;
+ u16 saved_dsxg_legacy;
+ u16 saved_dsxg_elegacy;
+#endif
+};
+
+int snd_ymfpci_create(struct snd_card *card,
+ struct pci_dev *pci,
+ unsigned short old_legacy_ctrl,
+ struct snd_ymfpci ** rcodec);
+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..0cd9b4029
--- /dev/null
+++ b/sound/pci/ymfpci/ymfpci_main.c
@@ -0,0 +1,2447 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of YMF724/740/744/754 chips
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include "ymfpci.h"
+#include <sound/asoundef.h>
+#include <sound/mpu401.h>
+
+#include <asm/byteorder.h>
+
+/*
+ * common I/O routines
+ */
+
+static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip);
+
+static inline u8 snd_ymfpci_readb(struct snd_ymfpci *chip, u32 offset)
+{
+ return readb(chip->reg_area_virt + offset);
+}
+
+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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->start_count++ > 0)
+ goto __end;
+ snd_ymfpci_writel(chip, YDSXGR_MODE,
+ snd_ymfpci_readl(chip, YDSXGR_MODE) | 3);
+ chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1;
+ __end:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_ymfpci_hw_stop(struct snd_ymfpci *chip)
+{
+ unsigned long flags;
+ long timeout = 1000;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (--chip->start_count > 0)
+ goto __end;
+ 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);
+ }
+ __end:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ * 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)
+{
+ unsigned long flags;
+ int result;
+
+ if (snd_BUG_ON(!rvoice))
+ return -EINVAL;
+ if (snd_BUG_ON(pair && type != YMFPCI_PCM))
+ return -EINVAL;
+
+ spin_lock_irqsave(&chip->voice_lock, flags);
+ for (;;) {
+ result = voice_alloc(chip, type, pair, rvoice);
+ if (result == 0 || type != YMFPCI_PCM)
+ break;
+ /* TODO: synth/midi voice deallocation */
+ break;
+ }
+ spin_unlock_irqrestore(&chip->voice_lock, flags);
+ return result;
+}
+
+static int snd_ymfpci_voice_free(struct snd_ymfpci *chip, struct snd_ymfpci_voice *pvoice)
+{
+ unsigned long flags;
+
+ if (snd_BUG_ON(!pvoice))
+ return -EINVAL;
+ snd_ymfpci_hw_stop(chip);
+ spin_lock_irqsave(&chip->voice_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&chip->voice_lock, flags);
+ 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;
+ spin_lock(&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--;
+ }
+ }
+ spin_unlock(&chip->reg_lock);
+}
+
+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;
+
+ spin_lock(&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);
+ }
+ }
+ spin_unlock(&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;
+
+ spin_lock(&chip->reg_lock);
+ if (ypcm->voices[0] == NULL) {
+ result = -EINVAL;
+ goto __unlock;
+ }
+ 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:
+ result = -EINVAL;
+ break;
+ }
+ __unlock:
+ spin_unlock(&chip->reg_lock);
+ 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;
+
+ spin_lock(&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;
+ }
+ spin_unlock(&chip->reg_lock);
+ 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;
+ unsigned long flags;
+
+ 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);
+ }
+ spin_lock_irqsave(&ypcm->chip->voice_lock, flags);
+ 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;
+ spin_unlock_irqrestore(&ypcm->chip->voice_lock, flags);
+ 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);
+
+ spin_lock_irq(&chip->reg_lock);
+ snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT,
+ snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) | 3 << 3);
+ spin_unlock_irq(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_ymfpci_ac3_done(struct snd_ymfpci *chip)
+{
+ spin_lock_irq(&chip->reg_lock);
+ snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT,
+ snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) & ~(3 << 3));
+ spin_unlock_irq(&chip->reg_lock);
+ // 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;
+ spin_lock(&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
+ spin_unlock(&chip->voice_lock);
+ spin_lock(&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);
+ spin_unlock(&chip->reg_lock);
+
+ 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(sizeof(*ypcm), GFP_KERNEL);
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ if (ypcm->output_rear) {
+ ymfpci_open_extension(chip);
+ chip->rear_opened++;
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_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++;
+ spin_unlock_irq(&chip->reg_lock);
+
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ ymfpci_open_extension(chip);
+ chip->rear_opened++;
+ spin_unlock_irq(&chip->reg_lock);
+ 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(sizeof(*ypcm), GFP_KERNEL);
+ 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;
+
+ spin_lock_irq(&chip->reg_lock);
+ if (ypcm->output_rear && chip->rear_opened > 0) {
+ chip->rear_opened--;
+ ymfpci_close_extension(chip);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_irq(&chip->reg_lock);
+ if (chip->rear_opened > 0) {
+ chip->rear_opened--;
+ ymfpci_close_extension(chip);
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ strcpy(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;
+ strcpy(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;
+ strcpy(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);
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_irq(&chip->reg_lock);
+ ucontrol->value.iec958.status[0] = 0x3e;
+ ucontrol->value.iec958.status[1] = 0xff;
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+
+ spin_lock_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;
+ spin_unlock_irq(&chip->reg_lock);
+ 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);
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_irq(&chip->reg_lock);
+ reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ oval = snd_ymfpci_readl(chip, reg);
+ val = (oval & ~(mask << shift)) | val;
+ change = val != oval;
+ snd_ymfpci_writel(chip, reg, val);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_irq(&chip->reg_lock);
+ val = snd_ymfpci_readl(chip, reg);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ spin_lock_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);
+ spin_unlock_irq(&chip->reg_lock);
+ 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return (mode >> pin) & 1;
+}
+
+static int snd_ymfpci_set_gpio_out(struct snd_ymfpci *chip, int pin, int enable)
+{
+ u16 reg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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)));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ 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;
+ spin_lock_irqsave(&chip->voice_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&chip->voice_lock, flags);
+ 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);
+ err = snd_ctl_add(chip->card, kctl);
+ if (err < 0)
+ return err;
+ kctl->id.device = chip->pcm_spdif->device;
+ kctl = snd_ctl_new1(&snd_ymfpci_spdif_mask, chip);
+ err = snd_ctl_add(chip->card, kctl);
+ if (err < 0)
+ return err;
+ kctl->id.device = chip->pcm_spdif->device;
+ kctl = snd_ctl_new1(&snd_ymfpci_spdif_stream, chip);
+ err = snd_ctl_add(chip->card, kctl);
+ if (err < 0)
+ return err;
+ kctl->id.device = chip->pcm_spdif->device;
+ 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 long flags;
+ unsigned int count;
+
+ chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ 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);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_ymfpci_timer_stop(struct snd_timer *timer)
+{
+ struct snd_ymfpci *chip;
+ unsigned long flags;
+
+ chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x00);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ 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) {
+ strcpy(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 */
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev,
+ size, &chip->work_ptr) < 0)
+ 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 int snd_ymfpci_free(struct snd_ymfpci *chip)
+{
+ u16 ctrl;
+
+ if (snd_BUG_ON(!chip))
+ return -EINVAL;
+
+ if (chip->res_reg_area) { /* don't touch busy hardware */
+ 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);
+
+ /* Set PCI device to D3 state */
+#if 0
+ /* FIXME: temporarily disabled, otherwise we cannot fire up
+ * the chip again unless reboot. ACPI bug?
+ */
+ pci_set_power_state(chip->pci, PCI_D3hot);
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+ kfree(chip->saved_regs);
+#endif
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ release_and_free_resource(chip->mpu_res);
+ release_and_free_resource(chip->fm_res);
+ snd_ymfpci_free_gameport(chip);
+ iounmap(chip->reg_area_virt);
+ if (chip->work_ptr.area)
+ snd_dma_free_pages(&chip->work_ptr);
+
+ release_and_free_resource(chip->res_reg_area);
+
+ pci_write_config_word(chip->pci, 0x40, chip->old_legacy_ctrl);
+
+ pci_disable_device(chip->pci);
+ release_firmware(chip->dsp_microcode);
+ release_firmware(chip->controller_microcode);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_ymfpci_dev_free(struct snd_device *device)
+{
+ struct snd_ymfpci *chip = device->device_data;
+ return snd_ymfpci_free(chip);
+}
+
+#ifdef CONFIG_PM_SLEEP
+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 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;
+
+ 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);
+ pci_read_config_word(chip->pci, PCIR_DSXG_LEGACY,
+ &chip->saved_dsxg_legacy);
+ pci_read_config_word(chip->pci, PCIR_DSXG_ELEGACY,
+ &chip->saved_dsxg_elegacy);
+ 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;
+
+ 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);
+
+ pci_write_config_word(chip->pci, PCIR_DSXG_LEGACY,
+ chip->saved_dsxg_legacy);
+ pci_write_config_word(chip->pci, PCIR_DSXG_ELEGACY,
+ chip->saved_dsxg_elegacy);
+
+ /* start hw again */
+ if (chip->start_count > 0) {
+ spin_lock_irq(&chip->reg_lock);
+ snd_ymfpci_writel(chip, YDSXGR_MODE, chip->saved_ydsxgr_mode);
+ chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT);
+ spin_unlock_irq(&chip->reg_lock);
+ }
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(snd_ymfpci_pm, snd_ymfpci_suspend, snd_ymfpci_resume);
+#endif /* CONFIG_PM_SLEEP */
+
+int snd_ymfpci_create(struct snd_card *card,
+ struct pci_dev *pci,
+ unsigned short old_legacy_ctrl,
+ struct snd_ymfpci **rchip)
+{
+ struct snd_ymfpci *chip;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_ymfpci_dev_free,
+ };
+
+ *rchip = NULL;
+
+ /* enable PCI device */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ chip->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;
+ chip->reg_area_phys = pci_resource_start(pci, 0);
+ chip->reg_area_virt = ioremap(chip->reg_area_phys, 0x8000);
+ pci_set_master(pci);
+ chip->src441_used = -1;
+
+ chip->res_reg_area = request_mem_region(chip->reg_area_phys, 0x8000, "YMFPCI");
+ if (!chip->res_reg_area) {
+ 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);
+ err = -EBUSY;
+ goto free_chip;
+ }
+ if (request_irq(pci->irq, snd_ymfpci_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, chip)) {
+ dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq);
+ err = -EBUSY;
+ goto free_chip;
+ }
+ chip->irq = pci->irq;
+ card->sync_irq = chip->irq;
+
+ snd_ymfpci_aclink_reset(pci);
+ if (snd_ymfpci_codec_ready(chip, 0) < 0) {
+ err = -EIO;
+ goto free_chip;
+ }
+
+ err = snd_ymfpci_request_firmware(chip);
+ if (err < 0) {
+ dev_err(chip->card->dev, "firmware request failed: %d\n", err);
+ goto free_chip;
+ }
+ snd_ymfpci_download_image(chip);
+
+ udelay(100); /* seems we need a delay after downloading image.. */
+
+ if (snd_ymfpci_memalloc(chip) < 0) {
+ err = -EIO;
+ goto free_chip;
+ }
+
+ err = snd_ymfpci_ac3_init(chip);
+ if (err < 0)
+ goto free_chip;
+
+#ifdef CONFIG_PM_SLEEP
+ chip->saved_regs = kmalloc_array(YDSXGR_NUM_SAVED_REGS, sizeof(u32),
+ GFP_KERNEL);
+ if (chip->saved_regs == NULL) {
+ err = -ENOMEM;
+ goto free_chip;
+ }
+#endif
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0)
+ goto free_chip;
+
+ snd_ymfpci_proc_init(card, chip);
+
+ *rchip = chip;
+ return 0;
+
+free_chip:
+ snd_ymfpci_free(chip);
+ return err;
+}